적당한 고통은 희열이다

- 댄 브라운 '다빈치 코드' 중에서

Swift iOS 앱 개발/Swift

[Swift ARC] Strong Reference Cycles 강한 순환 참조

hongssup_ 2023. 2. 7. 21:45
반응형
ARC가 참조 횟수를 추적하며 자동으로 메모리 관리를 해주지만,
강한 참조 횟수가 절대 0이 되지 않는 문제가 발생할 수도 있는데 이런 경우를 강한 순환 참조(strong reference cycle)라고 한다.
강한 순환 참조 문제는 두 클래스 인스턴스가 서로를 강한 참조하고 있을 때, 그리고 클로저에서 발생할 수 있다. 
이는 강한 참조 대신 weak 이나 unowned references 를 사용하여 해결할 수 있다. 

 

Strong Reference Cycles 강한 순환 참조

ARC가 참조 횟수를 추적하며 더 이상 참조하는 곳이 없으면 자동으로 메모리 해제를 해주지만,

강한 참조 횟수가 0이 되지 않아 메모리에서 해제되지 않고 계속 남아있는 경우를 강한 순환 참조라고 한다. -> memory leak 발생

 

강한 순환 참조가 발생할 수 있는 두 가지 경우 

- 클래스 인스턴스 간의 강한 순환 참조

- 클로저에서의 강한 순환 참조

 

Strong Reference Cycles Between Class Instances

Strong reference cycle can be created when two class instance properties hold a strong reference to each other, such that each instance keeps the other alive.

두 클래스 인스턴스가 서로에 대해 강한 참조를 하고 있을 경우, 각 인스턴스가 서로를 활성 상태로 유지하여 강한 순환 참조가 발생할 수 있다. 

이런 경우, 클래스 간의 관계 중 일부를 강한 참조 대신 약한 참조(weak reference) 또는 미소유 참조(unowned reference) 로 정의하여 강한 순환 참조 문제를 해결할 수 있다. 

 

실수로(?) 강한 순환 참조가 발생하는 과정

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

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

위와 같이 apartment 프로퍼티를 옵셔널로 가지는 Person 인스턴스와 tenant 프로퍼티를 옵셔널로 가지는 Apartment 인스턴스를 설정해준다. 그런 다음 다음과 같이 변수에 인스턴스를 할당하고 서로 연결시켜준다.  

var hongssup: Person?
var signiel: Apertment?

hongssup = Person(name: "Hongssup")
signiel = Apartment(unit: "70G")

hongssup!.appartment = signiel
signiel!.tenant = hongssup

이렇게 하여 내 아파트는 시그니엘이 되었고 시그니엘의 세입자는 내가 되었다! 🤭 크크킄 

The strong reference cycle prevents the Person and Apartment instances from ever being deallocated, causing a memory leak in your app.

그런데 이렇게 두 인스턴스를 연결시켜버리면 그 둘 사이에 강한 순환 참조가 생겨, 두 변수를 nil 값으로 변경하여도 각 인스턴스가 해제되지 못하고 memory leak 을 발생시킨다. 

how the strong references look after you link the two instances together
how the strong references look after you set the hongssup and signiel variables to nil

클래스 타입의 프로퍼티들에서 강한 순환 참조 해결 방법

- weak references : 프로퍼티가 옵셔널일 경우

- unowned references : 옵셔널이 아닐 경우. 

 

Weak Reference 

위의 예시에서 Person 과 Apartment 인스턴스가 강한 순환 참조 되는 것을 방지하기 위해 다음과 같이 프로퍼티 앞에 weak 키워드를 붙여 선언해준다. 세입자 프로퍼티는 값이 있을 수도, 없을 수도 있기 때문에 옵셔널 var 와 함께 weak 으로 선언해줄 수 있다. 

weak var tenant: Person?

이렇게 약한 참조로 설정해주면 signiel!.tenant = hongssup 으로 할당하더라도, 약한참조를 하므로 Person 인스턴스의 RC는 증가하지 않는다. 

Because a weak reference doesn’t keep a strong hold on the instance it refers to, it’s possible for that instance to be deallocated while the weak reference is still referring to it. Therefore, ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated. And, because weak references need to allow their value to be changed to nil at runtime, they’re always declared as variables, rather than constants, of an optional type.

 

Unowned Reference

an unowned reference is used when the other instance has the same lifetime or a longer lifetime.

Unlike a weak reference, an unowned reference is expected to always have a value. As a result, marking a value as unowned doesn’t make it optional, and ARC never sets an unowned reference’s value to nil.

다음과 같이 Customer, CreditCard 클래스 인스턴스를 생성할 때, CreditCard 의 customer 프로퍼티는 항상 값을 가지게 된다. 따라서 weak 이 아닌 unowned 로 설정을 하여 강한 순환 참조를 방지할 수 있다. 

class Customer {
    let name: String
    var card: CreditCard? //카드가 없을 수도 있으므로 옵셔널
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer //카드는 주인이 항상 있기 떄문에 옵셔널 아님
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

var hongssup: Customer?
hongssup = Customer(name: "Hongssup")
hongssup!.card = CreditCard(number: 12345678, customer: hongssup!)
hongssup = nil

 

Strong Reference Cycles for Closures

A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance. 

 

 


참고 : 

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

728x90
반응형