Retain Cycle과 그로 인한 Memory Leak을 방지하기 위해서는 weak과 unowned를 마주하는 것은 개발의 당연한 수순이다. 그리고 이 둘을 헷갈려하는 것도 자연스러운 수순이다. 이미 많은 글이 구글에 있기 때문에 자세한 설명은 그들에게 맡기고, 왜 이 둘 사이를 헷갈려하는지 어떻게 이해하는 것이 좋은지에 살펴보자.
핵심은 Reference Count가 0일 때, nil의 '보장' 여부를 정확하게 이해하는 것이다.
Objective-C에서는 ARC가 선택적이지만 Swift에는 무조건 ARC를 통해서 메모리를 관리하게 된다. ARC는 Complie 단계에서 자동으로 Reference Count(retain, release)를 분석해서 메모리 해제여부를 결정하는데, 이 때 weak와 unowned를 사용하면 Reference Count를 증가시키지 않은 채로 참조 할 수 있다. 참조를 하는데 Reference Count가 증가하지 않는다? 모순적인 이런 상황을 ARC는 이걸 어떻게 관리할까? weak나 unowned를 쓰는 대부분의 상황을 복기해보면 알겠지만, 이 둘의 존재는 Strong에 의존적이다. 다시 말해서 자신이 Reference Count를 굳이 증가시키지 않아도 weak와 unowned를 붙들고 있는 Strong의 해제 여부만으로도 이들의 해제 여부를 판단할 수 있다. 그런 면에서 어떤 강한 참조와도 엮이지 않은 아래와 같은 상황은 메모리에 등록되자마자 해제가 되는 아이러니한 일이 생기게 된다.
weak var a = a()
unowned var b = b()
Complier는 모순을 극도로 싫어하기 때문에 에러가 난다. 하지만 에러는 두 번째에만 난다. 왜냐하면 weak의 경우에는 var a가 가리키는 a()라는 객체가 nil일 수 있음을 가정(optional)하고 있기 때문이다. 그래서 실제로도 nil을 반환한다. 하지만 unowned는 nil임을 가정하지 않는다. 그렇기 때문에 위에서 모순을 피해갈 수 있는 요소가 아무 것도 없어진다. Strong Reference에 기댈 수도 없고, nil이라고 가정해주지도 않으니 말이다. 답이 없을 때는 에러가 답이다.
그래서 아래처럼 weak와 달리 unowned에는 Optional Chaining을 적용할 필요가 없다. unowned는 해당하는 객체에 Optional을 가정하고 있지 않기 때문이다.
// self가 옵셔널체이닝에 의해 연결되어 있다.
doSomething { [weak self] someText in
self?.myLabel.text = someText
}
// self가 옵셔널이 아니다.
doSomething { [unowned self] someText in
self.myLabel.text = someText
}
간단히 정리하자면 weak와 unowned 모두 Reference Count를 증가시키지 않으면서 참조 할 수 있는, Memory Leak을 방지하는 좋은 방법이다. 주의할 점은 Reference Count가 0이라고 항상 해당 객체의 nil을 '보장'하는 것이 아니라는 점이다. Reference Count가 0이면 모든 메모리는 해제가 된다. 하지만 그 상황 속에서 여전히 변수가 가리키는 객체가 nil을 보장하지 않는다면 모순이 일어나고 에러가 나게 된다. 참조하려는 객체가 nil에 대한 보장이 필요하다면 weak를 쓰고, 보장도 필요없고 unwrapping이 귀찮다면 unowned를 쓰면 된다.
'iOS 개발' 카테고리의 다른 글
WebRTC 3 : 다음 스텝을 위한 개념정리 (0) | 2021.07.21 |
---|---|
WebRTC 2 : Signaling Server (0) | 2021.07.21 |
WebRTC 1 : WebRTC가 뭐야? (0) | 2021.07.21 |
댓글