Strong, Weak, Unowned

박성민·2021년 2월 2일
0

iOS

목록 보기
12/31

Memory Leak

  • Retain cycle? : 메모리가 해제되지 않고 유지되어 누수가 발생하는 현상입니다.
  • App의 Memory Leak을 캐치하는 좋은 접근 방법은, 앱의 특정 flow를 여러번 반복하면서 메모리 그래프 디버거를 이용해 메모리 스냅샷을 찍어 비교하는 것 입니다.

Strong

  • 일반적인 참조(포인터와 같은 것들)이지만 ARC에 의해 해제되지 않도록 reference count을 1 증가시킨다는 점에서 특별합니다다. 일반적으로 어떠한 것이 객체를 strong 참조하는 동안 객체는 해제되지 않습니다.
  • property 선언에서 default로 strong 참조를 가집니다. 일반적으로 객체의 계층이 선형적 으로 이루어져 있을 때 안전하게 사용할 수 있습니다. 부모 → 자식으로 강한 참조 관계일 때 항상 ok입니다.

Weak

  • 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을 하지 않습니다.

  • 해결방법은 클로저의 캡쳐 리스트에서 self를 weak 참조하는 것입니다. 이는 강한 순환 참조를 끊습니다. weak 참조일 때는 ARC에 의해 reference count가 증가되지 않습니다.
//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

  • 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.
  • 클로저가 서로 상호의존적일 때 (하나가 다른 하나 없이는 live할 수 없을 때), weak 보다는 unowned를 쓰는 것이 프로그램이 불필요하게 nil 참조를 유지하는 overhead를 막아준다.

Side Table

  • Side Table은 추가적인 객체의 정보를 저장하는 별도의 메모리 공간이다.
  • 객체는 초기에 side Table을 가지고 있지 않는다. 객체가 weak 참조 될 때, strong이나 unowned카운터가 overflow 될 때 (32-bit 시스템에서 inline count는 작다) side Table이 생성된다.
  • Side Table과 객체는 서로 가리키는 포인터가 있다. 주목할 점은 strong과 unowned는 객체를 직접 가리킨다는 반면에, weak 참조는 side Table을 가리키고 있다. 이렇게 하면 객체의 메모리를 완전히 해제할 수 있다.

Swift Object Life Cycle

- Swift Object는 바로 소멸되지 않습니다. live -> deiniting -> deinited -> freed -> dead의 5단계 life cycle을 거칩니다.

참조 및 출처

profile
iOS시작~

0개의 댓글