아직은 개린이

[Swift] weak, unowned 본문

Swift + iOS/Swift

[Swift] weak, unowned

jiyeonlab 2020. 2. 27. 15:36

Swift의 ARC는 레퍼런스 카운트를 관리하여 메모리 누수를 방지하는 역할을 한다.

이 때, 인스턴스끼리 강한 순환 참조 문제를 해결하는 두가지 방법이 있는데 이에 대해 알아보고자 한다.

 


 

 

클래스 인스턴스의 강한 순환 참조를 해결할 수 있는 방법은 weak referenceunowned reference를 쓰는 것이다.

weak와 unowned는 strong 참조와 달리 레퍼런스 카운트를 세지 않는다.

 

Weak References

Weak Reference(약한 참조)는 참조하는 인스턴스를 강하게 유지하지 않는 참조이다. 

다른 인스턴스의 수명이 짧을 때 사용한다.

참조하는 인스턴스를 강하게 유지하지 않기 때문에 약한 참조를 하는 동안 해당 인스턴스가 할당 해제될 수 있다.

할당 해제가 되면 ARC는 자동으로 약한 참조를 nil로 설정한다. 

이렇게 약한 참조는 nil 값이 들어갈 수 있기 때문에 상수가 아닌 옵셔널 타입의 변수로 선언되어야 한다.

 

선언은 프로퍼티나 변수 선언 앞에 weak 키워드를 써준다.

Person 클래스와 Apartment 클래스를 예시로 살펴보자.

 

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 }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized")}
}

 

ARC 글에서 살펴보았던 예시와 비슷하지만, 다른 점이 한가지 있다.

Apartment 클래스의 tenant 변수를 weak로 선언해주었다는 것이다.

이 차이가 어떤 결과를 가져오는지 살펴보자.

 

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

위의 코드를 살펴보면,

john 변수와 Person 인스턴스, unit4A와 Apartment 인스턴스는 각각 강한 참조를 이루고 있다.

Person 인스턴스는 Apartment 인스턴스도 강한 참조를 이루고 있다.

 

하지만!

Apartment 인스턴스는 Person 타입의 변수인 tenant를 weak로 선언해줬기 때문에

Person 인스턴스에 약한 참조를 갖고 있다.

 

Apartment 인스턴스는 Person 인스턴스에 약한 참조를 갖는다.

이렇게 되면 john = nil 값을 넣어 john과 Person 인스턴스 사이의 참조를 끊게 되면,

더 이상 Person 인스턴스에는 강한 참조가 없어진다.

Person 인스턴스에 대한 참조가 더 이상 없으므로

할당이 해제되고

Apartment 인스턴스의 tenant도 nil로 설정된다.

unit4A와 Apartment 인스턴스 사이의 강한 참조만 남음.

unit4A 변수도 nil로 설정하게 되면,

Apartment 인스턴스에 대한 강한 참조까지 끊어지고, 할당 해제된다.

모든 참조가 끊어짐.

 

Unowned References

위에서 봤던 weak reference(약한 참조)처럼 unowned reference(미소유 참조)도 인스턴스 간에 강한 참조를 유지하지 않는다.

하지만 unowned reference는 다른 인스턴스의 수명이 동일하거나 길 때 사용한다.

또한, weak와 달리 항상 값이 있어야 하기 때문에 옵셔널이 아닌 상수로 선언한다.

 

선언은 weak와 마찬가지로 프로퍼티나 변수 앞에 unowned 키워드를 써준다.

Customer 클래스와 CreditCard 클래스 예시를 통해 살펴보자.

 

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")
    }
}

Custom 클래스에는 옵셔널 타입으로 CreditCard 속성이 있고,

CreditCard 클래스에는 unowned 키워드가 붙은 상수로 Custom 속성을 가지고 있다.

 

var john: Customer?

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

Customer 클래스 인스턴스로 john을 만들었고, john의 card 속성에 CreditCard 인스턴스를 할당했다.

이렇게 되면 아래와 같은 참조 관계가 생성된다.

john, Customer Instance, CreditCard Instance 사이의 참조 관계

위의 도식처럼 Customer 인스턴스는 CreditCard 인스턴스에 강한 참조를 갖고,

CreditCard 인스턴스는 소유하지 않은 Customer 인스턴스에 대한 참조를 갖는다.

 

 

하지만, john 변수의 강한 참조를 깨면,

아래 도식처럼 Customer 인스턴스에 대한 강한 참조가 더 이상 없어진다.

 

 

따라서, Customer 인스턴스에 대한 참조가 더 이상 없으므로 할당이 해제되고,

CreditCard 인스턴스에 대한 강한 참조도 더 이상 없어지므로 할당 해제된다.

john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"

 


 

ARC와 ARC 제어에 영향을 주는 키워드인 strong, weak, unowned에 대해 정리해보았다.

 

사실 이론적으로는 이제 이해가 완벽히 되었는데,

막상 내 코드에서 필요한 부분을 캐치하는 것에는 아직 연습이 필요한 것 같다.

 

코드를 작성할 때에 무작정 돌아가게만 하지 않고,

이런 부분까지도 고려하며 작성하는 습관을 들여야겠다.

 

클로저 내에서 weak, unowned 키워드가 쓰여야 하는 때가 있는데,

이에 대해서도 한 번 정리가 필요할 것 같다!!

 


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

'Swift + iOS > Swift' 카테고리의 다른 글

[Swift] ARC  (0) 2020.02.26
[Swift] CustomStringConvertible  (0) 2020.02.10
[Swift] lazy  (0) 2020.02.10