- 메모리 관리는 객체를 생성하면 메모리를 차지하게 되는데 메모리의 공간은 한계가 있기 때문에 필요한 객체가 차지하는 메모리는 유지하고 필요없는 객체의 메모리를 해제하여 공간을 효율적으로 사용하도록 하는 방법이다.
- 객체가 사용 중인지 여부는 레퍼런스 카운트(Reference Count)가 관리한다.
- 객체를 사용하게되면 레퍼런스 카운트가 증가되고, 사용이 끝나면 레퍼런스 카운트를 감소한다.
- 만약 레퍼런스 카운트가 0이 되면, 객체를 사용하는 부분이 없어 메모리에서 해제를 하는 방식으로 메모리를 관리한다.
- 레퍼런스 카운트는 이전에는 수동으로 관리를 했으나 현재는 ARC(Automatic Reference Count)를 이용하여 자동으로 관리한다.
- ARC(Automatic Reference Count)
- 객체의 레퍼런스 카운트 관리 코드를 자동으로 생성하여 관리한다.
- 레퍼런스 타입(클래스) 객체에만 적용한다.
- Value Type(구조체, enum) 객체에서는 적용되지 않는다.
- 객체의 메모리 소유와 해제
- 메모리 소유 : 객체 생성 시에 메모리를 소유하게 된다.객체 생성
class MyClass {
}
var ptr : MyClass! = MyClass()
// 객체를 생성하여 메모리를 소유한다. - 메모리 해제 : 객체에 nil을 대입하여 레퍼런스 카운트가 0이 되어 메모리에서 해제된다.객체 해제
var ptr : MyClass! = MyClass()
ptr = nil // 객체를 해제하여 메모리를 해제한다. - 강한 참조 : 객체에 nil을 대입하면 무조건 메모리가 해제되는 것이 아니라 레퍼런스 카운트가 0이되어 메모리가 해제되는 것이며, 다른 객체를 참조하고 있다면 레퍼런스 카운트가 0이 되지 않아 메모리 해제가 발생되지 않는다.강한 참조
var ptr : MyClass! = MyClass() // RC = 1
var anotherPointer = ptr // RC = 2
ptr = nil
// RC = 1로 레퍼런스 카운트가 0이 되지 않아 메모리에서 해제되지 않는다.
// RC = 0을 만들기 위해서 아래의 코드가 필요하다.
// anotherPointer = nil : RC = 0이되어 메모리 해제 발생 - 메모리 해제 확인은 deinit 메소드로 확인한다.메모리 해제 확인
class MyClass {
deinit {
print("메모리 해제")
}
} - 강한 참조로 순환 참조
- 앞에 강한 참조를 이용하여 서로 다른 클래스가 상대방을 소유하고 있다면 레퍼런스 카운트가 0이 되지 않아 메모리가 할당된 상태가 유지가 된다.
- 해당 상황을 표현한 그림은 아래와 같다.
- 위 상황을 보면 Car 클래스는 Engine 클래스의 객체를 생성하고, Engine 클래스는 Car 클래스의 객체를 생성하여 서로 상대방의 클래스의 객체를 생성하여 서로 레퍼런스 카운트를 증가시켜주고 있다.
- 코드로 나타내면 아래와 같이 된다.강한 순환 참조
class ClassA {
var objB : ClassB!
deinit {
print("ClassA 객체 해제")
}
}
class ClassB {
var objA : ClassA!
deinit {
print("ClassB 객체 해제")
}
}
var a : ClassA! = ClassA() // ClassA RC = 1
var b : ClassB! = ClassB() // ClassB RC = 1
a.objB = b // ClassB RC = 2
b.objA = a // ClassA RC = 2
a = nil
// ClassA RC = 1 : RC 가 0이 되지 않아서 메모리 할당유지
b = nil
// ClassB RC = 1 : RC 가 0이 되지 않아서 메모리 할당유지 - 약한 참조
- 강한 순환 참조는 서로 상대방 클래스의 RC 를 늘려 정작 본인 클래스의 메모리 해제 시에 RC 가 0이 되지 않아서 메모리 해제가 발생되지 않는데 객체를 소유하지 않는 약한 참조(weak, unowned)를 이용하여 강한 참조를 발생하지 않을 수 있다.
- weak
- 참조하던 객체가 해제가 되면 자동으로 nil이 되는 구조이다.
- nil이 되므로 옵셔널 타입으로 선언한다.
- 상호 독립적으로 존재하는 객체에 사용한다.
- 사용 방법약한 참조 : weak
// Person과 Phone이 서로 독립적인 존재
class Person {
weak var phone : Phone!
deinit {
print("Person 객체 해제")
}
}
class Phone {
var person : Person!
deinit {
print("Phone 객체 해제")
}
}
var person : Person! = Person() // Person RC = 1
var iphone : Phone! = Phone() // Phone RC = 1
// 순환참조
iphone.person = person // Person RC = 2
person.phone = iphone
// Phone RC = 1 : 약한참조로 소유하지 않음
// 객체 해제 확인
person = nil // Person RC = 1
iphone = nil // Phone RC 0, Person RC = 0
/* Person, Phone 클래스 모두 레퍼런스 카운트가 0이 되어 메모리 해제 발생 */ - unowned
- 참조하던 객체가 해제가 되어도 nil로 변하지 않아 Dangling Pointer의 위험이 있다.
- 옵셔널 타입으로 선언할 수 없어 Initializer 가 필요하다.
- 완전히 종속적인 경우에 사용한다.
- 사용 방법약한 참조 : unowned
class Country {
var capital : City!
}
class Capital {
unowned var country : Country
init(country:Country) {
self.country = country
}
}
var korea : Country! = Country() // Country RC = 1
var seoul : Capital! = Capital(country:korea)
// Country RC = 1 : 약한 참조로 소유하지 않음
// Capital RC = 1
// 순환 참조
korea.capital = seoul
// Capital RC = 2
// 객체 해제 확인
korea = nil // Country RC = 0
seoul = nil
// Capital RC = 1, Country RC = 0
// Capital RC = 0
/* Country, Capital 클래스 모두 레퍼런스 카운트가 0이 되어 메모리 해제 발생 */