Featured image of post WWDC21 - ARC in Swift: Basics and beyond

WWDC21 - ARC in Swift: Basics and beyond

img

스위프트는 ARC(Automatic Reference Counting)를 사용하여 앱의 메모리 사용량을 관리하고 추적한다. Swift를 효율적으로 사용하기 위해서 ARC가 동작하는 방식을 이해 해보자.

Object lifetimes and ARC

  • Object lifetimeinit()에서 시작되고 마지막으로 사용되면 끝난다.
  • ARC는 lifetime이 끝난 객체를 deallocate(할당 해제)한다.
  • ARC는 참조 카운트(reference count)를 사용하여 객체의 lifetime을 추적한다.
  • Swift 컴파일러가 retainrelease를 자동으로 삽입한다.
  • reference count가 0이 되면 객체는 deallocate된다.

img

위의 예제코드에서 Traveler의 참조가 시작되고 끝나는 지점을 확인할 수 있다. traveler1Traveler의 인스턴스를 생성하는 시점에 참조가 시작되고, traveler2traveler1을 참조하는 시점에 traveler1에 대한 사용이 종료되어 참조가 종료된다.

img

Swift 컴파일러는 init()되는 시점에 reference count가 1 올라가고(retain 생략), traveler1에 대한 사용이 끝난 지점에 release를 자동으로 삽입해 준다.

그렇다면 traveler2 인스턴스에 대한 참조는 어떻게 될까?

img

traveler2traveler1을 참조하는 시점에 traveler1에 대한 참조가 시작되고, traveler2의 destination에 “Big Sur"를 할당하는 시점에 traveler1에 대한 참조가 종료된다.

img

Swift 컴파일러가 시작 전 retain을 삽입하고, traveler2의 마지막 사용(destination에 “Big Sur"를 할당)후에 release를 삽입해 준다.

런타임에서 무슨 일이 일어나고 있는지 그림으로 다시 확인해 보자.

img

Traveler 객체는 초기화되면서 Heap영역에 생성되고, reference count가 1로 증가한다.

img

traveler1traveler2에 할당하면서 traveler1의 reference count가 2로 증가한다.

img

treveler1의 사용이 완료되어 release가 호출되면서 reference count가 1로 감소한다.

img

traveler2destination에 “Big Sur"를 할당되어 계속해서 treveler2가 사용되고 있으므로 reference count는 1로 유지된다.

img

traveler2의 사용이 완료되어 release가 호출되면서 reference count가 0이 되고 메모리에서 해제된다.

object lifetime in Swift

스위프트에서 객체의 lifetime은 사용기반(use-based)으로 결정된다. 객체의 lifetime은 초기화되는 시점부터 마지막 사용까지 보장된다.

img

실제 수명 주기는 컴파일러가 자동으로 삽입해주는 retainrelease에 의해 결정되므로, 최소한으로 보장된 수명주기와 달라질 수 있다.

Observable object lifetimes

대부분의 경우 객체의 정확한 수명은 중요하지 않을 수 있다.
하지만 weak, unowned를 사용하거나 Deinitializer side effects와 같은 기능을 사용하는 경우 객체의 수명을 관찰해야 할 필요가 있다.

img

생성된 객체가 해제되지 않고 오랫동안 남아있을 가능성도 있고, 컴파일러가 업데이트 되거나 소스코드가 변경되면서 문제가 발생할 가능성이 있다.

weak and unowned references

weakunowned 참조는 reference count를 증가시키지 않는다. 따라서 보통 reference cycle을 방지하기 위해 사용한다.

reference cycle 참조 사이클?

img

위의 TravelerAccount 클라스를 살펴보자, Traveler는 account 프로퍼티를 통해 Account를 가지고 있고, Account는 traveler 프로퍼티에서 Traveler를 가지고 있다. 서로가 서로를 참조하고 있기 때문에 reference cycle이 발생한다.

아래 그림과 같이 test함수를 통해 Traveler 타입과 Account 타입의 인스턴스를 생성하고 lifetime을 살펴보자.

img

Heap 영역에 Traveler 타입 인스턴스가 생성되며 reference count는 1로 올라간다.

img

다음으로 account 프로퍼티를 통해 Account 타입 인스턴스가 생성되며 reference count는 1로 올라간다. account는 Traveler타입을 참조하으로 Traveler 인스턴스의 reference count는 2로 증가한다.

img test함수의 3번째 라인을 통해 Traveler 타입의 account 프로퍼티에 Account 인스턴스를 할당한다. 이때 Account 인스턴스의 reference count는 2로 증가한다.

img Account 타입의 마지막 사용이 끝났기 때문에 Account 인스턴스의 reference count는 1로 감소한다.

img test함수의 4번째 라인을 통해 Traveler 타입의 마지막 사용이 끝났기 때문에 Traveler 인스턴스의 reference count 역시 1로 감소한다.

모든 사용이 다 종료 되었지만, Traveler 인스턴스와 Account 인스턴스는 reference count가 0이 되지 않고 계속해서 1로 남아있기 때문에 메모리에서 해제되지 않고 계속해서 남아있으면서 메모리 누수(memory leak)을 발생시킨다. 이러한 상황을 reference cycle 이라고 한다.

weak, unowned references

reference cycle을 해결하기 위해 weakunowned 참조를 사용할 수 있다. 메모리에서 해제된 인스턴스를 참조하려고 하면 weak reference 로 선언된 객체는 nil을, unownded reference로 선언된 객체는 trap을 반환한다.

img 위의 예제에서 Account 타입의 traveler 프로퍼티를 weak로 선언하면 Traveler 인스턴스의 reference count는 0이 되어 메모리에서 해제된다.

Traveler 인스턴스가 해제되면서 acount 프로퍼티에 할당된 reference도 해제되기 때문에 Account 인스턴스의 reference count는 0이 되어 메모리에서 해제된다.

지금까지는 weak를 참조를 끊어주는데(break) 사용했지만, 만약 weak를 객체에 접근할 때 사용한다면 오류를 발생시킬 수 있다.

img

printSummary 메서드는 Account로 이동하였다. test()함수의 traveler.account = account에서 traveler 인스턴스가 마지막으로 사용되고 메모리에서 해제되면 account 인스턴스는 traveler 인스턴스에 접근할 수 없게 된다.

img

img

Reference

WWDC21 - ARC in Swift: Basics and beyond
Swift Doc - Automatic Reference Counting

Licensed under CC BY-NC-SA 4.0