티스토리 뷰

오늘은 ARC 즉, Automatic Reference Counting에 대해 정리해보려 합니다.

스위프트 문서 참고

ARC란? - Swift는 ARC를 활용해 앱의 메모리 사용을 추적하고 관리합니다. 인스턴스가 더 이상 필요하지 않으면 메모리에서 해제합니다. 그렇기 때문에 우리는 메모리 관리에서 비교적 자유로워질 수 있죠 ㅎㅎ 하지만, 강한 참조로 순환이 이루어질 때는 메모리 누수가 발생할 수 있기 때문에 우리가 적절한 조치를 해줘야 합니다. 지금은 무슨 말인지 잘 모르겠어도 뒷부분에 설명할 예정이니 걱정 안 하셔도 됩니다!

작동 방식 - ARC는 각 인스턴스가 얼마나 참조되고 있는지를 추적하고 저장함으로써 메모리 관리를 합니다. 만약 참조 counting이 1 이상이면 인스턴스는 메모리에 계속 남아 있고 0이 되었을 때 비로소 메모리에서 해제됩니다. 참조 counting은 (강한) 참조될 때 +1 이 됩니다. 

Ex)

만약 위와 같이 Person 클래스가 있고 Person 인스턴스에 대해 3번 참조하고 있으므로 reference count = 3이 되게 됩니다. 

deinit은 메모리에서 해제될 때 실행되게 되는데요. 다음과 같이 Person 인스턴스를 참조하던 변수에 nil을 입력하게 되면 참조 횟수가 1 줄어둘게 되고, 참조 횟수가 0이 되었을 때 메모리에서 Person 인스턴스가 해제되면서, deinit이 호출 됩니다. 여기서 deinit메서드 안에 print를 찍어봄으로써 실제로 메모리에서 해제되었는지 확인해볼 수 있습니다. 

 

위 예제에서도 봤듯이 ARC가 알아서 메모리를 관리해주기 때문에 우리는 메모리 관리에 큰 신경을 쓰지 않아도 되는데요. 하지만 강한 참조 순환이 발생할 경우 Reference count가 0이 되지 않기 때문에 메모리에서 영원히 해제되지 않게 됩니다. 

강한 참조 순환(Strong Reference Cycle)이란 두 개의 인스턴스가 서로를 강하게 참조하고 있을 때 발생합니다. (일반적인 참조가 강한 참조) 

이것도 예제로 살펴보는 것이 이해가 쉬우니 Swift 공식 문서의 예제를 통해 같이 살펴봅시다!

위와 같이 코드를 작성하게 되면 강한 순환 참조가 되어 ARC가 알아서 인스턴스를 메모리에서 해제하지 못하게 됩니다. 

이를 그림으로 도식화 하면 다음과 같은데요.

보시다시피 Person 인스턴스의 Reference count = 2, Apartment 인스턴스의 Reference count = 2가 되게 됩니다.

그래서 lee = nil, unit4A = nil을 해도 둘 다 Reference count = 1 이기 때문에 이 두 개의 인스턴스는 메모리에서 해제되지 않는 거죠. 그럼 바로 메모리 누수로 이어지게 됩니다. ㅜㅜ

그럼 이런 경우는 어떻게 메모리 누수를 방지하냐? -> 약한 참조(weak)와 미소유 참조(unowned)를 사용하면 됩니다. 

약한 참조란 참조를 강하게 유지하지 않는 참조로, Reference count를 올리지 않는다고 생각하면 쉽습니다. 변수 앞에 weak을 선언함으로 써 사용 가능합니다.

Ex) 

위의 강한 참조가 발생하는 예제에서 다음과 같이 var tenant: Person?를 weak var tenant: Person? 으로 바꿔줌으로써 강한 참조 사이클로부터 벗어날 수 있습니다. 

이를 그림으로 도식화하면 다음과 같습니다.

Apartment 인스턴스에 대한 reference count = 2, Person 인스턴스에 대한 Reference count = 1이 되죠.

이제 똑같이 각각의 인스턴스에 nil을 대입하면 두 인스턴스 모두 메모리에서 해제되는 것을 볼 수 있습니다.

 

그럼 미소유 참조(Unowned Reference)는 뭘까요?? 

미소유 참조 역시 약한 참조와 마찬가지로 참조를 강하게 유지하지 않음으로써 강한 참조 순환이 발생하는 것을 방지합니다. 다만 약한 참조와의 차이점은 미소유 참조의 경우 다른 인스턴스의 수명이 같거나 수명이 더 길 때 사용됩니다. 또 약한 참조와 다르게 미소유 참조는 항상 값을 가질 것으로 예상되어야 합니다. 

Swift 문서를 보면 다음과 같이 주의 사항을 안내하고 있습니다. 

" 참조가 항상 할당 해제되지 않은 인스턴스를 참조한다고 확신하는 경우에만 미소유 참조를 사용하세요. 인스턴스가 할당 해제된 후 미소유 참조의 값에 액세스 하려고 하면 런타임 오류가 발생합니다."

미소유 참조도 텍스트로 읽을 때는 언뜻 이해가 가지 않으니 예제로 살펴보져 ㅎㅎ;;

Ex)

다음과 같이 Customer 클래스와 Card 클래스가 있습니다.

이전과의 차이점이라면 이전의 변수 선언과 달리 unnowned let 으로 상수 선언이 되어 있는데요.

아까 미소유 참조의 경우 다른 인스턴스의 수명이 같거나 수명이 더 길 때 사용하고 항상 값을 가질 것으로 예상되어야 한다고 했죠? 사용자의 경우 카드를 소지할 수도 안 할 수도 있지만 (즉, 옵셔널로 설정) 카드의 경우 무조건 사용자가 있어야 합니다. (값이 항상 있으므로 Not Optional) 또 위의 경우 Card 인스턴스는 결코 Customer 인스턴스의 수명보다 오래 지속될 수 없습니다. (Customer 인스턴스가 메모리에서 해제될 때 무조건 Card인스턴스 역시 메모리에서 해제되니까요 ㅎㅎ) 

그림으로 도식화하면 다음과 같습니다.

출력

 

Closure를 사용할 때도 강한 순환 참조에 주의해야 합니다. Swift 문서는 다음과 같이 설명하고 있습니다.

"클래스 인스턴스의 속성에 클로저를 할당하고 해당 클로저의 본문이 인스턴스를 캡처하는 경우에도 강력한 참조 주기가 발생할 수 있습니다. 이 캡처는 클로저의 본문이 self.someProperty와 같은 인스턴스의 속성에 액세스 하거나 클로저가 self.someMethod ()와 같은 인스턴스의 메서드를 호출하기 때문에 발생할 수 있습니다. 두 경우 모두 이러한 액세스로 인해 클로저가 자신을 "캡처"하여 강력한 참조 주기를 생성합니다."

"이 강력한 참조 주기는 클래스와 같은 클로저가 참조 유형이기 때문에 발생합니다. 속성에 클로저를 할당하면 해당 클로저에 대한 참조를 할당하는 것입니다. 본질적으로 위와 동일한 문제입니다. 두 개의 강력한 참조가 서로를 유지하고 있습니다. 그러나 두 개의 클래스 인스턴스가 아니라 이번에는 서로를 유지하는 클래스 인스턴스와 클로저입니다."

Ex) 다음과 같이 어떤 Service라는 Class가 있고 싱글턴 패턴으로 사용하려 합니다. (메모리에 계속 남아 있겠죠?) 이제  ClosureTestController에서 Service의 fetchData 메서드를 사용할 때 자기 자신을 강한 참조로 참조한다면 강한 순환 참조가 생기게 됩니다. 즉, ClosureTestController를 ARC가 알아서 메모리 해제하지 못한다는 거죠. 

 

따라서 다음과 같이 클로저 안에 [weak self]라고 capture list를 추가해줘야 합니다. 

 

글이 엄청 길어지네요 ㅎㅎ;;

클로저뿐만이 아니라 Delegate Pattern을 사용할 때도 memory leak이 발생할 수 있는데요.

다음과 같은 경우입니다. 

위와 같이 delegate 패턴을 사용한다고 가정할 때, delegate변수를 weak으로 선언하지 않으면 강한 순환 참조가 생기기 때문에DelegateTestController, DelegateTestView 둘 다 메모리에서 해제되지 않습니다. 

제가 이 블로그에서 설명한 모든 테스트는 따로 프로젝트로 만들어서 깃헙에 올려놓았으니, 글로 이해가 잘 안 되시는 분들은 직접 다운로드하여 실행시켜 보시기 바랍니다. 

긴 글 읽어주셔서 감사합니다 :)

 

참고)

1. Swift Docs - ARC

docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

 

Automatic Reference Counting — The Swift Programming Language (Swift 5.3)

Automatic Reference Counting Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yours

docs.swift.org

2. 민소네님 블로그

minsone.github.io/mac/ios/swift-automatic-reference-counting-summary

 

[Swift]Automatic Reference Counting 정리

자동 참조 계수(Automatic Reference Counting) Swift는 앱의 메모리 사용을 추적하고 관리하는 자동 참조 계수(ARC)를 사용. 대부분의 경우에 메모리 작업은 잘 작동하며, 메모리 관리를 생각할 필요 없다.

minsone.github.io

3) Youtube - iOS Academy

www.youtube.com/watch?v=chI-B8u4MBs

 

4) Youtube - Let's Build That App

www.youtube.com/watch?v=q0-DIJszYRo

 

댓글
링크
최근에 올라온 글
최근에 달린 댓글