티스토리 뷰

반응형

 

안녕하세요 Zedd입니다.

그냥 갑자기 이런 의문이 들었어요.

Strong Reference Cycle을 해결하는 방법이 weak와 unowned를 사용하는거잖아..weak는 진짜 많이 쓰는데 unowned는 진짜 한번도 안써본듯...

unowned를 적재적소에 쓸 수 있다면 좋을텐데..!!! 

그래서 unowned에 대한 고찰..이라는 제목으로 시작했지만

일단은 ARC에 대해서 정리를 한번 하고 가는게 좋을 것 같아서요 :D 

 

# ARC

- Automatic Reference Counting의 약자

- Swift의 메모리 사용량 추적 및 관리 시스템. 

- ARC는 더이상 필요하지 않은 클래스 인스턴스를 자동으로 메모리에서 해제한다. 

 

Q : ARC는 특정 인스턴스가 더이상 필요하지 않군..을 어떻게 알아?

A : RC(Reference Count)를 통해 알 수 있음

 

인스턴스를 어딘가에서 사용중(참조중)인데 그걸 메모리에서 해제하면 안되겠죠?

그래서 이 인스턴스가 참조되고 있는 count를 기록합니다.

그리고 그 count가 0이 되면

ARC는 이 인스턴스를 참조하고 있는 곳이 하나도 없구나! 하고 

자동으로 이 인스턴스를 메모리에서 해제합니다.

 

예를 통해 보겠습니다. 

상수 또는 변수에 / 클래스 인스턴스를 할당 할 때 / 해당 인스턴스에 대한 참조가 생깁니다.

class이므로 stack에는 heap을 가리키는 포인터가 저장되게 됩니다.

그러니까 Heap메모리 영역을 참조하게 되는거죠.

위에서 RC는 1이 되게 됩니다.

참고로 

https://developer.apple.com/videos/play/wwdc2016/416/?time=2817

RC는 Heap영역에 같이 저장됩니다.

point2라는 상수를 만들어 point1. 즉 클래스 인스턴스를 할당합니다.

상수 또는 변수에 / 클래스 인스턴스를 할당 할 때 / 해당 인스턴스에 대한 참조가 생깁니다.

그러므로 역시나 참조가 하나 더 생기게 되고 

RC는 2가 됩니다.

 

해당 인스턴스를 확고하게 유지하고 강력한 참조가 유지되는 한 할당 해제를 허용하지 않기 때문에

이를 강한(Strong) 참조라고 합니다. 

상수 또는 변수에 / 클래스 인스턴스를 할당 할 때 / 해당 인스턴스에 대한 강한 참조가 생깁니다.

라고 할 수 있겠네요.

힘세고 강한 아침!

 

# Strong Reference Cycles Between Class Instances

Swift 공식문서에서 개많이 보셨을

Person과 Apartment입니다.

john과 unit4A라는 인스턴스를 만들었습니다.

지금 대충 이런 그림이 된거임. 

자..위 그림을 보시면

john의 apartment, unit4A의 tenant가 nil입니다.

왜냐?

Optional의 기본값이 nil이기 때문이죠! 

john의 apartment, unit4A의 tenant에 서로를 넣어줍니다.

상수 또는 변수에 클래스 인스턴스를 할당 할 때, 해당 인스턴스에 대한 참조가 생깁니다.

라고 그랬죠!? 기본적으로 강한(strong) 참조이기 때문에

이렇게 Heap영역에서 서로가 서로를 참조하게 됩니다.

위에서 만들었던 john과 unit4A를 nil로 해도 각각의 deinit이 호출되지 않습니다.

왜냐? 

여기서 서로를 강하게 참조하고 있기 때문이죠 == RC가 0이 아니기 때문이죠

 

ARC : ㅎㅎ!! 이건 RC가 0이 아니네!? 사용중이니까 내가 해제 안할께!? 🥺

Zedd : **

 

저 강한 참조를 해제하고 싶어도, 저 프로퍼티에 접근 할 수 있는 john과 unit4A가 

nil이 되어버렸기 때문에...접근도 못함

이런 상황을 강한 순환 참조(Strong Reference Cycles)라고 합니다.

영원히 메모리에 남아있게 됩니다. (흔히 하는 말로..메모리 누수(leak)가 난다고 그러죠..!?) 

 

이 강한 순환 참조를 해결할 수 있는 방법이 weak와 unowned를 사용하는 것입니다.

 

# weak 

말그대로 약한 참조입니다.

✔️ 특 : RC를 증가시키지 않음.

네! 강한 참조는 RC를 무조건 증가시켰지만 weak는 RC를 증가시키지 않습니다

tenant를 weak로 선언해주고,

unit4A의 tenant에 john을 넣으면 이제 참조가 시작되는데

약하게(weak) 참조하는 것이죠

 

Q : Person의 apartment에 weak선언하면 안돼?

A : 네 당연히 됩니다!! 근데 애플은 왜 tenant를 weak로 선언했을까요? 

 

Apple : 다른 인스턴스의 수명이 더 짧은 경우 즉, 다른 인스턴스를 먼저 할당 해제 할 수있는 경우 약한 참조를 사용합니다.

 

 tenant는 거주자, 입주자라는 뜻인데, 아파트에 거주자, 입주자가 아직 없을 수 있겠죠? 

프로그램이 잘 돌아가다가 아파트의 입주자가 나갈 수도 있겠죠. 즉 먼저 할당해제가 될 수 있다는 말입니다.

그래서 tenant를 weak로 선언한겁니다. 

john을 nil로 선언합니다. 이제는 deinit에 넣어놨던 print가 찍히는 군요. 

자 Person 인스턴스 입장에서 나를 참조하고 있는게

이렇게 2가지입니다. 총 2개의 참조가 있지만 하나는 weak이기 때문에 RC는 1입니다.

john을 nil로 선언하는 순간

저 강한 참조가 끊어지게 됩니다. 

저 강한 참조때문에 RC가 1인거였는데, 저게 끊어지면서 RC가 0이 됩니다.

 

ARC : 응 해제야~

 

RIP..

 

✔️ 특 : 참조하는 인스턴스가 메모리에서 해제되면 / 자동으로 weak 참조를 nil로 설정

자 그리고 한가지 weak의 특성을 살펴봐야하는데요. 

 

또 위에서 했던 똑같은 이야기 할건데요. 위 그림의 오른쪽 빨간 박스를 주목해주세요!! 

apartment의 tenant에는 Person 인스턴스가 있었습니다. 

그런데 john을 nil로 할당하면서

== Person 인스턴스에 대한 유일한 강한 참조가 끊어짐

== RC가 0이 됨

== ARC는 RC가 0인 친구들을 메모리에서 자동으로 해제하므로

Person Instance가 메모리에서 해제가 되어버립니다.

그러면!! ARC는 참조하는 인스턴스가 해제되면 자동으로 weak 참조를 nil로 설정합니다. 

 

자 프로그램이 잘 돌아가다가 아파트에 john이 들어가서 거주자로 살게되고 john이 죽었습니다..??

런타임에 apartment의 tenant가 nil이 되어버린거죠.

 

✔️ 특 : 런타임에 값이 nil로 변경될 수 있으므로 항상 Optional로 선언되어야함. 

✔️ 특 : 런타임에 값이 nil로 변경될 수 있으므로 항상 var로 선언되어야함.

 

 

# unowned 

✔️ 특 : RC를 증가시키지 않음.

아까 weak설명할 때

Apple : 다른 인스턴스의 수명이 더 짧은 경우 즉, 다른 인스턴스를 먼저 할당 해제 할 수있는 경우 weak(약한) 참조를 사용합니다.

라고 그랬잖아요? 

unowned는 weak와 반대로

다른 인스턴스의 수명이 동일하거나 수명이 더 긴 경우 unowned(미소유) 참조를 사용합니다.

 

✔️ 특 : 참조하는 인스턴스가 메모리에서 해제되어도 자동으로 nil로 만들어주지 않음. 

자..weak는 위에서 봤다시피 참조하는 동안 해당 인스턴스가 할당 해제될 수 있었죠? 

바로 이렇게 말이죠. 

하지만 unowned 참조는 weak 참조와 달리 항상 값을 가질 것으로 예상합니다. 

즉, ARC는 이 unowned값을 nil로 설정하지 않습니다.

이런 상황인겁니다.

저 tenant에 들어있는 값은 이미 메모리에 없는것이죠. 

(포인터가 해제된 메모리 영역을 가리키고 있고 이러한 포인터를 dangling pointer라고 합니다.)

 

근데 만약 tenant에 접근하려고 한다?  

응 안돼~~ 

✔️ 특 : crash를 발생시킬 수 있으므로 조심해서 사용해야함. 

 

자..그럼 unowned를 사용하려면 어떤 상황이어야 할까요? 추측해봅시다.

네 내가 unowned로 참조하고 있는 인스턴스가 메모리에서 나보다 먼저 해제가 안되면 되는거에요! 

그래서 

Apple : 다른 인스턴스의 수명이 동일하거나 수명이 더 긴 경우 unowned(미소유) 참조를 사용합니다.

이렇게 말한거구요!! 

 

그래서 우리가 봤던 Person과 Aparment예제는 unowned를 사용하기 적절하지 않아요.

그래서 애플이 준비한 예제.. 

고객과 신용카드입니다.

고객은 신용카드가 없을 수 있습니다.

하지만 신용카드가 고객이 없을 수 있을까요? 항상 신용카드는 고객과 연결된다는 것을 알 수 있습니다. 

Apple : 다른 인스턴스의 수명이 동일하거나 수명이 더 긴 경우 unowned(미소유) 참조를 사용합니다.

즉 신용카드보다 고객이 먼저 없어지는 경우는 없을테니, 

신용카드의 고객을 unowned로 선언합니다.

대충 고객을 만들고 고객의 신용카드에 만든 신용카드 인스턴스를 넣어줍니다. 

내가 고객을 참조하긴 참조할건데 unowned로 하겠어

(왼쪽의 Customer 인스턴스의 RC는 1, 오른쪽 신용카드의 RC역시 1입니다.)

john을 nil로 할당하는 순간 

위 강한(strong) 참조가 없어지면서 

== RC가 0이 되면서

== 메모리에서 해제.

customer 인스턴스가 메모리에서 해제되면서

신용카드 참조하는 것도 당연히 없어지게 되고,

신용카드 인스턴스에 대한 강한 참조가 하나도 없기 때문에

신용카드 인스턴스 역시 메모리에서 해제가 됩니다. 

그래서 john을 nil로 하자마자 두개의 클래스 deinit이 모두 불린것이죠!!

 

자...구구절절 말이 길어졌는데, unowned는 참조하고 있는 친구를 nil로 만들지 않는다는 특징이 있잖아요?

즉 unowned 프로퍼티는 nil이 될 일이 없어요. 

그래서 unowned 프로퍼티를 보시면 옵셔널이 아닙니다.

그리고 한가지 특징이 하나 더 있습니다.

✔️ 특 : 런타임에 값이 nil로 변경되지 않으므로 let으로 선언가능(물론 var로도 선언가능)

 

unowned는 참조하고 있는 친구를 nil로 만들지 않음.

➞ 런타임에 nil이 될 일이 없음 

➞ let으로 선언가능

그래서 위 그림을 보시면

let으로 선언되어 있는 것을 볼 수 있습니다.

 

자..여기서 

아하~ weak는 옵셔널이 되어야 하지만 unowend는 아니구나! 옵셔널이 아니어야해!! 

라고 이해하실 수 있습니다. 위 말은 Swift 5.0전까지는 맞는말이었습니다.

실제로 문서에서도 

unowned는 non-optional타입이어야해 라고 했으니까요.

하지만 Swift 5.0 Released! 에서 

unowned 프로퍼티가 이제 Optional타입을 지원합니다.

응 쌉가능이야~~~

 

Q : 아니 unowned는 nil로 안만드는데 Optional로 선언할 필요가 있ㄴ ㅏ...?? Optional로 선언하면 모가 달라지는 부분..?

A : 이건 저도 지금 잘 이해가 안가는데 나중에 따로 글을 쓰도록 할게요 🤯

 

틀린 부분을 발견하셨다면 댓글 부탁드립니다! 

 

글을 쓰다가

- Retain Count, Reference Count

- Retain Cycle, Reference Cycle에 대해 생각해봤어요.

단어를 혼동해서 쓰면 안될 것 같아 일단 공식문서에 나와있는 Reference~로 사용했어요! 

Swift공식 문서에는 retain이라는 단어는 하나도 없는데 왜 retain~ 으로도 많이 쓰일까..!?가 궁금해서

찾아봤는데 Transitioning to ARC Release Notes 문서에서 

You should take advantage of these qualifiers to manage the object graphs in your program.
In particular, ARC does not guard against strong reference cycles (previously known as retain cycles—see Practical Memory Management).
Judicious use of weak relationships will help to ensure you don’t create cycles. 

reference cycles(previously known as retain cycles~) 이라고 나와있더라구요

예전에는 retain cycle이라고 불렀나봐요! 

궁금증 해결,,,

반응형

'Swift' 카테고리의 다른 글

Key-Value Coding(KVC) / KeyPath in Swift  (4) 2021.02.28
Unowned Optional References  (11) 2021.02.22
Swift ) 정렬 (3) - Sort by multiple criteria  (1) 2020.11.24
Swift ) 정렬 (2) - Sort by two criteria  (0) 2020.11.24
Swift ) 정렬 (1)  (0) 2020.11.23