Memory Leak
- Retain cycle? : 메모리가 해제되지 않고 유지되어 누수가 발생하는 현상입니다.
- App의 Memory Leak을 캐치하는 좋은 접근 방법은, 앱의 특정 flow를 여러번 반복하면서 메모리 그래프 디버거를 이용해 메모리 스냅샷을 찍어 비교하는 것 입니다.
weak 참조는 ARC로 부터 객체가 해제되는 것을 막지 않습니다. 또한 객체가 할당 해제되면 포인터는 nil이 됩니다. 이렇게 하면 weak 참조는 항상 객체에 접근할 때 유효한 객체이거나 nil이거의 상태가 된다.
모든 weak 참조 변수는 let이 아닌 var여야 한다. → 참조되고 있지 않은 객체는 nil로 변경되기 때문입니다. (mutable)
weak 참조는 retain cycle이 일어날 가능성이 있을 때 사용하는 것이 중요합니다. 두 객체가 서로를 strong하게 참조하고 있으면, ARC는 두 객체에 적절한 release message를 전달할 수 없습니다.
예를 들어, 클로져 범위 밖에 변수가 선언되어 있고, 클로져 범위 내에서 그 변수를 참조하면 또 다른 strong 참조가 생성되게 된다. 단, value semantics(Ints, Strings, Arrays, Dictionaries 등)를 가지는 것들은 예외.
class Kraken {
var notificationObserver: ((Notification) -> Void)?
init() {
notificationObserver = NotificationCenter.default.addObserver(
forName: "humanEnteredKrakensLair",
object: nil,
queue: .main
) { notification in
self.eatHuman()
}
}
deinit {
NotificationCenter.default.removeObserver(notificationObserver)
}
}
NotificationCenter는 eatHuman()을 호출할 때 self를 strong참조하는 클로져를 유지하고 있습니다. deinit을 할 때 observer를 지우는 것이 적절하겠지만, 해당 observer는 kraken에 의해 strong 참조 관계를 가지기 때문에 ARC에서 deinit을 하지 않습니다.
//Look at that sweet, sweet Array of capture values.
let closure = { [weak self, unowned krakenInstance] in
self?.doSomething() //weak variables are Optionals!
krakenInstance.eatMoreHumans() //unowned variables are not.
}
또 한가지 경우는 클래스에서 delegation을 지정하기 위해 프로토콜을 사용할 경우입니다. struct나 enum도 프로토콜을 채택할 수 있지만 클래스와 달리 value semantics이다.
delegate property를 weak로 선언하여 retain cycle을 방지할 수 있지만, 이 경우의 protocol은 class를 상속받아 reference semantics임을 밝혀야 한다.
weak reference는 객체의 lifetime에 nil이 될 수 있는 경우, 반대로 참조가 nil이 되지 않을 때 즉, 참조하고 있는 객체와 같거나 더 긴 lifetime을 가지고 있을 때는 unowned reference를 쓰라고 apple 문서에 적혀있다.
unowned는 weak와 유사하게 reference count를 증가시키지는 않지만, optional 값이 아니라는 이점을 가지고 있다. 이는 optional binding의 수고를 덜어준다.
객체가 해제되고 나면, dagling pointer가 된다.
Unowned를 사용해되 되는 예시
class RetainCycle {
var closure: (() -> Void)!
var string = "Hello"
init() {
closure = { [unowned self]
self.string = "Hello, World!"
}
}
}
//Initialize the class and activate the retain cycle.
let retainCycleInstance = RetainCycle()
retainCycleInstance.closure()
//At this point we can guarantee the captured self inside the closure will not be nil. Any further code after this (especially code that alters self's reference) needs to be judged on whether or not unowned still works here.