Zeroing & Object Life Cycle (feat: Side Table)

이원희·2021년 2월 22일
0

 🐧 Swift

목록 보기
24/32
post-thumbnail

오늘은 zeroing과 객체 생명주기에 대해서 알아보자.
zeroing은 ARC에서 스치듯 지나갔다.

Zeroing

Zeroing을 이해하기 위해서는 weak에 대해서 알고 있어야한다.
weak의 특징을 간단하게 알아보자.

  • weakreference counting을 증가시키지 않는다.
  • weaknil이 할당될 수 있어 var와 Optional을 사용해야한다.

weak으로 선언하면 참조하고 있는 것이 먼저 메모리에서 해제되기 때문에
ARC는 weak으로 선언된 참조 대상이 해제되면 런타임에 자동으로 참조하고 있는 변수에 nil을 할당한다.
(ARC에서 약한 참조에 nil을 할당하면 프로퍼티 옵저버는 실행되지 않는다.)

Zeroing은 위에서 말하는 참조 대상이 해제되며 런타임에 자동으로 참조하고 있는 변수에 nil을 할당하는 과정을 뜻한다!


왜 ARC에서 weak 참조에 nil을 할당하면 프로퍼티 옵저버가 실행되지 않을까?

Side Table에 대해서는 ARC에서 약한 참조에 nil을 할당하면 프로퍼티 옵저버는 실행되지 않는다의 이유를 찾다 여기까지 와버렸다...ㅋㅋㅋ

class Person {
    let name: String
    var apartment: Apartment?
    init(name: String) {
        self.name = name
    }
    deinit{
        print("\(name) is deinit")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? = nil {
        didSet {
            print("change")
        }
    }
    init(unit: String) {
        self.unit = unit
    }
    deinit{
        print("\(unit) is deinit")
    }
}

var John: Person?
var unit4A: Apartment?

John = Person(name: "John")
unit4A = Apartment(unit: "4A")

John!.apartment = unit4A
unit4A!.tenant = John

John = nil
unit4A = nil

우선 코드를 봐보자.
Apartment의 tenant 프로퍼티에 옵저버를 붙였다.
didSet 옵저버를 붙였으니 tenant 프로퍼티의 값이 변할때마다 "change"가 출력되야 한다.

그렇다면 언제 옵저버가 작동할까?
Apartment 클래스 인스턴스인 unit4A의 tenant가 변할때일 것이다.
unit4A!.tenant = John에서 한 번 "change"가 출력될 것이고,
John = nil 이후에는 출력될까?

Apartment의 tenant 프로퍼티는 weak참조로 해당 프로퍼티가 참조하고 객체가 메모리에서 해제되면 zeroing을 통해 nil이 반환되어 저장될 것이다.
그렇다면, John = nil 코드 이후에는 unit4A의 tenant 프로퍼티는 nil이 될 텐데 옵저버가 실행되어야하지 않을까?

change
John is deinit
4A is deinit

ARC에 의해 nil이 지정되는 경우에는 프로퍼티 옵저버가 실행되지 않는다.
그렇다면 이유가 있을거 같은데 공식문서에도

이렇게만 나와있다...!ㅋㅋㅋ
(너무 불친절해...ㅋㅋㅋㅋ)

그래서 이리저리 알아보다 Side Table과 Object Life Cycle에 대해 정리해보려 한다.


Side Table

ARC를 공부하면서 Reference Count는 어떻게 관리될까에 대해 알아봤었다.
클래스 인스턴스가 생성되면 HeapObject가 생성되는데 이때 HeapObject에는 인스턴스의 정보를 포함해 RC도 같이 저장된다고 했다.

그래서 HeapObject에 대해서 한 번 봐봤다!

Reference Count를 어떻게 관리하는지 볼 수 있는 Swift Code이다.

HeapObject가 어떻게 구성되어 있는지 확인할 수 있다.
HeapObject 구성을 보면 InlineRefCountBits에는 strong RC + unowned RC + flags / HeapObjectSideTableEntry 라고 써있다.
Inline이라고 하니 HeapObject 내부에 구성되어 있을거 같다.
InlineRefCountBits에는 strong, unowned는 있는데 weak은 없다..
그렇다면 HeapObjectSideTableEntry에 있나 확인해보자.

HeapObjectSideTableEntry에는 weak RC가 있다.
흠... 또 pointer를 가지고 있다.

코드 설명에 따르면,

weak RC는 object에 대한 약한 참조를 계산한다.
object 할당이 해제된 후, weak RC가 0이 되면 object의 side Table entry가 해제된다.

처음 Object에는 Side Table이 없다.
그렇다면 언제 Object에 Side Table이 생길까?
객체가 weak 참조될 때 혹은 strong이나 unowned RC가 오버플로될때 생긴다고 한다.

Strong and unowned variables point at the object.
Weak variables point at the object's side table.

이 부분이 가장 흥미로웠다.
strong, unowned는 object를 가리킨다.
weak는 side table을 가리킨다.

이런 그림으로 설명할 수 있을거 같다.

weak, zeroing, property observer

그럼 이제 다시 내가 궁금해하던거에 대해서 생각해보자.

ARC에서 약한 참조에 nil을 할당하면 프로퍼티 옵저버는 실행되지 않는다.

weak 참조는 Object를 가리키지 않고, side table을 가리키고 있기 때문에 ARC에 의해서 Object가 해제되면 weak 입장에서는 옵저버를 실행할 수 없다고 생각했다.
옵저버와 관련된 내용은 Object 정보에 들어있을테니..!

내가 추측한 바로는 그런데 혹시 틀리거나 다르게 생각하면 언제든 의견을 남겨주면 좋겠다!


Object Life Cycle

Object의 생명 주기는 위와 같다고 한다.
하나씩 알아보자!

LIVE

LIVE 상태는 object가 살아있는 상태이다.
weak 참조가 없다면 side table이 없다.
strong RC가 0이되면 deinit()을 호출하고 object는 DEINITING 상태가 된다.

DEINITING

DEINITING without side table

Object에서 deinit()이 진행되고 있는 중이다.
unowned 변수는 swift_abortRetainUnowned()에서 load가 중단된다.
unowned 변수 저장소는 정상적으로 작동한다.

ARC 포스팅에서 unownednil을 할당할 수 없고, 항상 Object가 메모리에 올라와 있다고 생각하고 사용한다고 했다.(unowned Optional이 아닌 경우)
따라서 Object가 deinit()되면 swift_abortRetainUnowned()를 통해 Object에 대한 load는 중단하지만 nil이 아니므로 저장소는 그대로인거 같다.

weak 변수 저장소에는 nil을 저장한다.
deinit()이 완료되면 swift_deallocObject를 호출한다.
swift_deallocObjectcanBeFreedNow()를 호출해 빠르게 weakunowned 참조가 없는지 확인한다.

canBeFreedNow()의 결과값이 true라면 Object는 FREED를 거쳐 DEAD된다.
canBeFreedNow()의 결과값이 false라면 unowned RC를 감소시키고 Object는 DEINITED 상태가 된다.

DEINITING with side table

weak 변수에 nil을 반환한다.
weak 변수 저장소는 nil을 저장한다.

canBeFreedNow()는 항상 false이므로 DEAD로 전환되지 않는다.
나머지는 위의 DEINITING 과정과 동일하다.

DEINITED

DEINITED without side table

deinit()은 완료되었지만 unowned 참조는 처리되지 않은 상태이다.
strong 참조는 이미 처리되어 있는 상태이다.

unowned 변수는 swift_abortRetainUnowned()에서 load 중지된다.
side table이 없는 경우이므로 weak 참조가 없는 상태이다.
그러므로 unowned RC가 0이 되면 Object가 해제되고 DEAD된다.

DEINITED with side table

weak 변수에 nil을 반환한다.
unowned RC가 0이 되면, 그 Object는 아직 weak 참조가 refs가 있으므로 FREED 상태가 된다.
나머지는 위의 DEINITED 과정과 동일하다.

FREED

FREED without side table

FREED 상태는 DEINITED 상태에서 weak 참조 refs가 남아있는 경우에 진입 하므로 이 상태는 절대 일어날 수 없다.

FREED with side table

Object는 해제되었지만 side table에 대한 weak 참조가 남아있는 상태이다.
(weak 참조는 Object를 가리키지 않고, side table을 가리키고 있으므로)
weak 참조가 0이 되면 side table이 해제되고 Object가 DEAD 상태가 된다.

DEAD

Object와 side table이 모두 없어진 상태이다.


마무리

오늘은 Zeroing과 Side Table과 Object Life Cycle에 대해서 알아봤다.
한 3일동안 ARC에 대해서 알아봤다.... 힘들군ㅋㅋㅋ
그럼 이만👋

2개의 댓글

comment-user-thumbnail
2021년 5월 11일

크 잘봤습니다🙋🏻‍♂️

답글 달기
comment-user-thumbnail
2021년 10월 6일

다 뜯어보셨네요. 대박..! 잘 읽고 갑니다~!

답글 달기