본문 바로가기
iOS/궁금증

[iOS] ARC_객체 수명, weak와 unowned 차이

by MINT09 2024. 3. 21.

 

Swift에서 struct와 같은 값 타입이 아니라, class와 같은 참조 타입을 사용하면 참조 카운트, Reference Count가 발생하게 됩니다. 이는 애플에서 메모리를 관리하는 방법으로 메모리를 할당하거나, 참조할 때 카운트를 증가시키고 사용을 완료하면 감소시킵니다.

Objective -C에서는 이를 개발자가 직접 관리했었고, 그것을 MRC Manual Reference Counting 이라고 부릅니다.

그리고 현재 Swift에서는 직접적으로 참조 카운트를 삽입하고 해제하는 코드를 작성할 필요 없습니다. complier가 컴파일 타임에 자동으로 구문 분석을 통해 알아서 관련 코드를 삽입하여 메모리 관리를 해주는데 이를 ARC Automatic Reference Counting이라고 합니다.

그렇다면 정말 이 자동이라는 말처럼 메모리에 신경쓰지 않아도 되느냐. 아닙니다. 서로가 서로를 참조하게 된다면 각각의 변수에서는 할당을 해제했음에도 서로 참조 카운트를 올리고 있어 메모리에서 내려가지 않고 남아있게 됩니다.

때문에 저희는 이 객체의 수명이 끝났는지를 관찰하기 위해 관찰된 객체 수명(Observed Object’s lifetime)을 사용하고는 합니다. 여기에는 Weak, Unowned, Deinit과 같은 키워드들이 있는데 특히 Weak와 Unowned를 사용하면 카운트를 올리지 않기 때문에 순환 참조를 방지할 수 있습니다.

weak & unowned

다만 이들은 참조 카운트를 올리지 않아서 Weak나 Unowned가 사용되는 동안 참조된 개체의 할당이 취소될 수 있습니다. 그렇게 되면 Swift Runtime은 weak에 대한 access를 nil로, unowned에 대한 access를 trap으로 전환합니다.

왜 이런 차이가 있을까요?

애초에 메모리에 저장될 때부터 차이가 있습니다. weak를 사용하면 해당 포인터는 unowned처럼 저장되어 있는 HeapObject를 가리키는 것이 아니라 object Pointer를 지니고 있는 SideTable이라는 것이 생성되면서 HeapObjectSideTableEntry를 가리키게 됩니다.

Side Table은 weak 키워드를 사용하였을 때, Strong과 unowned의 참조 카운트가 오버플로우 되었을 때, 객체가 관련하여 추가적인 공간이 필요할 때 생기는 것으로 스레드 간의 Race Condition을 막기 위해 one-way Operation으로 생성됩니다.

때문에 참조된 객체의 할당이 취소되어 Deinited 상태가 되더라도 side table이 남아있기 때문에 nil을 반환받을 수 있는 겁니다. 다만 이렇게 받는다고 값을 다시 사용하거나 할 수는 없습니다. side table은 어디까지나 객체를 가리키는 포인터로 추가적인 공간일 뿐이기에 흔적이 있는 것일 뿐, 해당 객체에 대한 메모리가 존재하진 않습니다.

다만 이러한 관찰된 객체 수명을 사용하는 것은 앞서 말했듯 객체 수명을 보장하지 않기 때문에 꽤나 위험할 수 있습니다.

ARC WWDC의 예제에서 보면 test 함수의 마지막에 account.printSummary()가 실행될 수 있지만, 이는 우연한 일일 뿐입니다. traveler 객체의 마지막 사용이 해당 함수 이전이라, 객체 수명이 끝났기 때문입니다. 제대로 작동한다면, 이는 nil로 변경되어 crash가 날 것입니다.

Solution

이를 해결하기 위해서는 다음과 같은 방법이 있습니다.

1. withExtendedLifetime()

withExtendedLifetime()을 사용하면 객체의 수명을 명시적으로 연장할 수 있습니다. 연장하고 싶은 코드를 클로저에 넣거나, 혹은 연장하고 싶은 라인 밑에 작성하는 방법으로 사용이 가능합니다. defer를 사용하여 해당 함수가 종료될 때까지 객체 수명을 연장시키는 것도 하나의 방법이겠죠.

이는 마치 MRC와 같은 방법으로 개발자에게 책임이 요구되는 방법입니다. 때문에 관찰된 객체 수명으로 버그가 생길 가능성이 있을 때마다 사용할 수 있도록 해야할 것입니다. 자칫하다간 유지관리 비용이 증가하게 될 수 있습니다.

2. 더 나은 API로 재설계

혹은 애초부터 버그가 일어나지 않도록 설계하는 방법도 있습니다.

3. 트리 구조로 변경

ARC WWDC에서는 애초부터 weak나 unowned 키워드를 사용하는 일 없이 class 구조를 설계하는 것이 있을지도 모르는 버그들을 제거하는 확실한 방법이라고 합니다. 물론 사용해야하는 순간들이 있으니 만들어졌을 테니 무조건적으로 사용하지 말자!는 아닌 것 같구요. 이러한 사항들을 잘 생각하면서 코드를 작성하면 좋을 것 같습니다.

 

https://developer.apple.com/videos/play/wwdc2021/10216/

 

ARC in Swift: Basics and beyond - WWDC21 - Videos - Apple Developer

Learn about the basics of object lifetimes and ARC in Swift. Dive deep into what language features make object lifetimes observable,...

developer.apple.com

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting#app-top

 

Documentation

 

docs.swift.org

https://github.com/apple/swift/blob/main/stdlib/public/SwiftShims/swift/shims/RefCount.h

 

swift/stdlib/public/SwiftShims/swift/shims/RefCount.h at main · apple/swift

The Swift Programming Language. Contribute to apple/swift development by creating an account on GitHub.

github.com

https://shubhamchawla00.medium.com/memory-management-on-ios-ad2244b49b20

 

Memory management on iOS

Memory Management

shubhamchawla00.medium.com

https://jeonyeohun.tistory.com/373

 

[Swift] strong, weak, unowned의 비밀

래퍼런스 카운트 안녕하세요! 오늘은 swift에서 래퍼런스 카운트를 어떻게 구현하고 있는지 알아보려고 합니다. 많은 블로그에서 weak과 unowned의 차이를 Optional의 가능 여부라고 말하고 있는데, 사

jeonyeohun.tistory.com