[Swift] The difference between `weak` and `unowned` (via Side Table of RefCount.h)

Benedicto·2024년 6월 27일
0

iOS

목록 보기
17/29

Swift는 순환 참조 문제를 해결하기 위해 weakunowned 키워드를 제공한다.

weak와 unowned는 '참조하는 클래스 인스턴스의 RefCnt를 증가시키지 않는다' 라는 공통점이 있지만, 'Optional 가능 여부' 라는 면에서 차이점이 있다.

💡 Swift 5.0부터 unowned에 optional을 지원하게 되면서, optional 가능 여부는 더 이상 의미가 없어졌다.

weak와 unowned optional의 디테일한 차이를 고민하면서 알아보던중,
[Stack Overflow] - 'Optional unowned reference versus weak in Swift 5.0' 이곳에서 힌트를 얻었다.

오버헤드와 시간/공간/계산의 복잡성에서 차이가 있는것 같다

그래서 Swift의 Repo의 RC와 관련된 RefCount.h 를 통해 RC에 대해 공부해 보았다.


RefCount.h

🗣️ object는 개념적으로 3개의 refcounts를 갖는다. refcounts는 isa 다음 필드에 "inline"으로 저장되거나, isa가 포인터를 가지고 있는 "side table entry"에 저장 된다.

• Strong RC

: strong RC는 object에 대한 strong reference하는 개수를 계산한다. strong RC가 0에 도달하면 object는 deinit이 되고, 미소유 참조 (unowned reference) 접근 시 error를, 약한 참조 (weak reference) 접근 시 nil이 된다.
strong RC는 extra count (추가 카운트)StrongExtraRefCount에 저장되며 '물리적 필드 0은 논리적 값 1' 이다.

• Unowned RC

: unowned RC는 object에 대한 unowned reference하는 개수를 계산한다. unowned RC는 강한 참조를 대신해 추가적인 +1 값을 갖고, 이 +1 값은 deinit이 완료되면 감소한다.
unowned RC는 0에 도달하면 'object의 할당을 해제 (= freed)' 한다.

• Weak RC

: weak RC는 object에 대한 weak reference하는 개수를 계산한다. weak RC는 미소유 참조를 대신해 추가적인 +1 값을 갖고, 이 +1 값은 object의 할당 해제가 되고 난 뒤 감소한다.
weak RC는 0에 도달하면 'object의 side table entry가 해제' 된다.

object가 처음 생성될 때는 side table이 없다. 객체는 아래와 같은 경우에 side table을 얻을 수 있는데,

  • a weak reference is formed and pending future implementation:
    -> 약한 참조가 형성되었으며, 향후 구현을 대기 중일 때

  • strong RC or unowned RC overflows (inline RCs will be small on 32-bit)
    -> strong RC 또는 unowned RC가 overflow될 때

  • associated object storage is needed on an object
    -> object와 연관된 추가적 저장 공간이 필요할 때

  • etc

side table entry는 one-way operation으로 생성되며, object가 side table entry를 가지게 되면 절대 잃어 버릴수 없다.
이는 'thread races (race condition 또는 race hazard)를 막기 위함' 이다.

strongunonwedobject를 직접 가리키며 (참조), weakobject의 side table을 가리킨다.


• Layout of 'HeapObject' and 'Side Table'

object는 최적화를 위해 "inline" 으로 refcounts를 저장하며,

  • CASE 1. Weak 참조가 아닐경우 ❌:
    strong RC, unowned RC, flags 가짐

  • CASE 2. Weak 참조일 경우 ✅:
    HeapObjectSideTableEntry 의 포인터 가짐
    ↪ HeapObjectSideTableEntry 안에서 strong RC, unowned RC, weak RC, flags 가짐

Understanding Swift Side Tables

ref. Discover Side Tables - Weak Reference Management Concept in Swift


• <Deep Dive - HeapObject, HeapObjectSideTableEntry>


• Object lifecycle state machine:

  • CASE 1. side table이 존재 하지 않는 경우의 lifecycle ❌ (strong, unowned)

    • LIVE without side table

      • object의 refcounts는 strong: 1, unowned: 1, weak: 1의 값을 가지고 초기화 된다.
      • side table, weak RC storage 모두 존재하지 않고, Strong varUnowned var의 연산은 정상적으로 작동한다.
      • Weak variable은 load 될 수 없다.
        (만약 약한 참조가 발생하면) Weak var는 side table을 추가하고 저장한 뒤, LIVE with side table 상태가 된다.
      • strong RC가 0에 도달하면 deinit()이 호출되어 object는 DEINITING 상태가 된다.
    • DEINITING without side table

      • object의 deinit()이 진행 중이므로, Strong var 연산에는 영향을 미치지 않는다.

      • Unowned var의 load는 swift_abortRetainUnowned() 에 의해 중단되지만, store에 있어서는 정상적으로 작동한다.

        swift_abortRetainUnowned(): runtime 시, unowned 참조가 더 이상 유효하지 않은 객체에 접근하려고 할 때 호출되는 함수

      • Weak var는 load 될 수 없으며, stores에 nil을 저장한다.

      • deinit()이 완전히 끝나면, swift_deallocObject를 호출하고 swift_deallocObject는 canBeFreedNow() 를 호출하여 weakunowned 참조가 있는지 없는지 빠르게 확인한다.

        True: object는 freed되어 DEAD 상태가 된다.
        False: unowned RC를 감소시키고 object는 DEINITED 상태가 된다.

    • DEINITED

      • deinit()이 끝났으나 아직 미소유 참조가 남아있다.
      • Strong var 연산과 Unowned var 저장은 불가능하다.
      • Unowned var의 load는 swift_abortRetainUnowned() 에 의해 중단되며, Weak var 연산도 불가능하다.
      • unowned RC가 0에 달하면, object는 해제 (freed)되고, DEAD 상태가 된다.
    • FREED

      • side table이 없으면서 FREED 상태는 존재하지 않으므로, 일어날 일이 없음.
    • DEAD

      • object와 side table이 제거됨.

  • CASE 2. side table이 존재하는 경우의 lifecycle ✅ (weak)

    • LIVE with side table

      • Weak var에 대한 연산 처리가 발생하는 것 이외에는 LIVE without side table과 동일
    • DEINITING with side table

      • weak var load는 nil을 반환하고 stores에 nil을 저장한다.
      • canBeFreedNow() 는 항상 False 값 이므로, 곧바로 DEAD 되지 않는다.
      • 나머지는 DEINITING과 똑같다.
    • DEINITED

      • Weak var load는 nil을 반환하고, 저장은 불가능하다.
      • unowned RC가 0에 도달하면, object는 freed되고, weak RC는 감소하면서 object는 FREED 상태가 된다.
      • 그 외에는 DEINITED와 동일
    • FREED

      • object는 해제되었지만 side table에 약한 참조가 남아있다.
      • Strong varUnowned var 연산은 불가능하다.
      • Weak var load는 nil을 반환하고, 저장은 불가능하다.
      • weak RC가 0에 도달하면, side table entry는 freed 되고 object는 DEAD 상태가 된다.
    • DEAD

      • object와 side table이 제거됨.

• Schematic object lifecycle state machine

object의 lifecycle을 도식화
ref. Memory management on iOS


Reference


More Deep Dive

위 내용을 배경으로 더 깊게 알아가고자 현재 아래의 references를 참고하여 인스턴스의 RC가 시프트 연산자를 사용하여 메모리에 어떻게 저장되는지 알아보고 있다.

Reference

profile
 Developer

0개의 댓글