[소유 정책]
1. 모든 객체는 생성될 때 참조 카운트가 1이 된다.
2. retain 메세지를 보내면 참조 카운트가 1 증가한다. 이 메시지를 보낸 호출자는 객체를 '소유' 한다고 한다.
3. release 메시지를 보내면 참조 카운트가 1 감소한다. 이 메시지를 보낸 호출자는 객체의 소유권을 포기한다.
4. autorelease 메시지를 보내면 현재 사용 중인 오토릴리즈 풀 블록의 실행이 종료되는 시점에 참조 카운트가 1이 감소한다.
5. 참조 카운트가 0이 되면 객체의 메모리가 해제된다.
[오토릴리즈 풀]
오토릴리즈 풀은 autorelease 메시지를 받은 객체가 해지되기 전까지 저장되는 공간이다.
이 공간에 저장된 객체들은 오토릴리즈 풀이 해제될 때 다함께 release된다.
각 스레드 하나에 오토릴리즈 풀 스택이 있고, autorelease 메시지를 받은 객체가 자신의 스레드 중 최상위 풀에 소유된다.
[강한 참조와 문제]
강한 참조는 우리가 흔히 쓰는 참조이다.
class A{
var b : B?
deinit {
print("A is deinit")
}
}
class B{
var a : A?
deinit {
print("B is deinit")
}
}
var a : A? = A()
var b : B? = B()
a?.b=b
b?.a=a
a = nil
b = nil
cs
A 클래스의 객체 a 와 B 클래스의 객체 b 는 서로를 참조하고 있다.
a 가 가르키고 있는 A 객체는 참조 카운트가 2이다. 왜냐면 a 가 참조하고 있고, b 안에 있는 프로퍼티 a 가 참조하고 있기 때문이다.
b 가 가르키고 있는 B 객체도 2이다.
a 와 b 를 둘 다 nil로 바꿔주고 이제 더 이상 참조를 할 방법이 없지만, 메모리가 해제되지 않았다.
참조 카운트가 1개씩 남아있기 때문이다.
이런 상황은 메모리 누수이고 피해야할 상황이다.
이 것이 강한 참조의 문제이다.
[약한 참조]
약한 참조는 weak 키워드를 변수에 붙여서 약한 참조로 만드는 것이다. 이는 무조건 Optional 자료형에만 사용 가능하다.
class A{
var b : B?
deinit {
print("A is deinit")
}
}
class B{
weak var a : A?
deinit {
print("B is deinit")
}
}
var a : A? = A()
var b : B? = B()
a?.b=b
b?.a=a
a = nil
b = nil
//A is deinit
//B is deinit
cs
B 의 객체 안에 속성 a 를 weak 키워드를 붙여 약한 참조로 만들어줬을 뿐인데 메모리가 정상적으로 해제되었다.
원리는 다음과 같다.
B 객체의 속성 a 는 A 객체를 약한 참조하기 때문에 A 객체는 참조 카운트가 1이다. B 객체는 참조 카운트가 2이다.
A 객체를 nil 을 할당하여 해제하였으므로 A 객체는 참조 카운트가 0이 되어 해제되고, 해제되는 과정에서 A 객체 내의 속성 b 는 B 객체에 대한 참조를 포기한다.
그 결과, B 객체도 참조 카운트가 1이 된다.
그리고 B 객체도 nil 을 할당하여 해제하니 모두 해제가 완료되었다.
[비소유 참조]
unowned 라는 키워드를 속성 선언 앞에 붙여서 선언할 수 있는데, 이는 약한 참조와 반대로 Optional 자료형이 아닌 값만을 가질 수 있다.
참조 대상에 대한 강한 참조를 유지하지만 항상 유효한 대상을 참조한다고 확신하는 것이다.
이는 비소유 참조이고, 참조 사이클을 형성하지 않고 해제된다.
class A{
var b : B?
deinit {
print("A is deinit")
}
}
class B{
unowned var a : A
init(a : A){
self.a=a
}
deinit {
print("B is deinit")
}
}
var a : A? = A()
var b : B? = B(a : a!)
a?.b=b
a = nil
b = nil
//A is deinit
//B is deinit
cs
[클로저의 강한 참조 사이클]
클로저도 참조 형식이다.
예를 들어, 클래스 내에 하나의 속성이 클로저를 참조하고 있다고 해보자.
그 클로저 내부에서 self 라는 키워드를 사용하면 클로저는 자기 자신객체를 참조하게 되므로 참조 사이클이 형성된다.
그럴 땐 클로저 캡쳐 목록을 이용해 해결할 수 있다.
클로저 캡쳐 목록은 클로저 중괄호 안의 대괄호 [] 사이에 weak 혹은 unowned 키워드를 써주고 캡쳐대상들을 쉼표로 분리해서 나열해준다.
{
[weak 캡쳐 대상들] (파라미터 목록) -> 리턴형 in
클로저에서 실행할 코드
}
class A{
var a : Int = 0
lazy var closure = {
[unowned self] () in
print(self.a)
}
deinit {
print("A is deinit")
}
}
var a : A? = A()
a?.closure()
a = nil
//0
//A is deinit
cs
이 스니펫을 보면, closure 라는 클로저를 담는 속성을 lazy로 선언해준 이유는 자기 자신의 초기화가 완료되지 않고 self 란 값을 쓸 수 없기 때문이다.
그리고 self 란 값을 그냥 써주면 강한 참조가 발생하여 A 객체가 제대로 해제되지 않지만,
클로저의 캡쳐 목록을 사용했기 때문에 제대로 해지가 된 모습이다.
'iOS' 카테고리의 다른 글
iOS - Closure 최적화 (0) | 2020.04.30 |
---|---|
iOS - JSON Parsing (0) | 2020.04.27 |
iOS - Cocoa Pods (0) | 2020.04.09 |
iOS - Custom Fonts (0) | 2020.04.01 |
iOS - ImagePicker (이미지 피커) (0) | 2020.03.27 |