THIS IS ELLIE

Swift ARC에 대해서(3) 본문

개발/Swift

Swift ARC에 대해서(3)

Ellie Kim 2019. 4. 20. 04:42

https://hyerios.tistory.com/33 앞에서 말한 메모리 누수의 원인인 순환 참조는 어떻게 해결할 수 있을까요 

 

이를 해결하기 위한 방법은 두가지가 존재합니다.

 

- weak reference 약한 참조

- unowned reference 미소유 참조

 

즉 강한 참조로 하지 않고도 약한 참조와 미소유 참조로도 서로를 참조할 수 있습니다.

 

그럼 여기서 드는 궁금증

 

Q. weak, unowed reference의 차이점은 뭐야!

 

약한 참조는 옵셔널이며 미소유 참조는 옵셔널이 아닙니다.  

 

Q. weak, unowed reference는 언제 사용할까요!

 

약한 참조는 다른 인스턴스의 수명이 짧을 때 사용합니다.

미소유 참조는 다른 인스턴스의 수명이 같거나 긴 경우 사용합니다.

 

weak reference 약한 참조

 

약한 참조는 참조하는 인스턴스를 강하게 참조하지 않습니다.

그렇기 때문에 약한 참조가 참조하는 동안 해당 인스턴스가 할당 해제될 수 있습니다.

ARC는 인스턴스가 해제될 때 약한 참조를 자동으로 nil로 설정합니다.

(약한 참조는 런타임에 값을 변경할 수 있어야 하므로 무조건 변수 var로 선언돼야 합니다.)


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

여기서 주목할 점은 tenant 입니다.

tenant가 약한 참조로 선언되어 있습니다.


var john: Person?
var unit4A: Apartment?

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

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

위 코드는 이전에 나온 예제와 동일하지만 한 가지 차이점은 약한 참조로 인스턴스를 참조하는 것입니다.

 

Person인스턴스는 강한 참조로 Apartment인스턴스를 참조합니다.

Apartment인스턴스는 약한 참조로 Person인스턴스를 참조합니다.

 

john의 강한 참조를 끊어주면 Person인스턴스에 대한 강한 참조가 존재하지 않습니다.


john = nil

위 코드에 의해 Person인스턴스가 할당 해제되고, John Appleseed is being deinitialized가 출력됩니다. 

 

 

결과적으로 Person 인스턴스에 대한 강한 참조가 더 이상 없기 때문에 할당이 해제됩니다.

Person 인스턴스를 참조하는 tenant 속성은 위 그림과 같이 ARC에 의해 nil로 설정됩니다. 

 

이제 unit4A변수의 Apartment인스턴스에 대한 강한 참조만 존재합니다. 


unit4A = nil

unit4A의 강한 참조를 끊어봅니다.

Apartment 4A is being deinitialized가 출력됩니다.

Apartment인스턴스에 대한 강한 참조가 없기 때문에 Apartment인스턴스는 할당 해제됩니다.

 

 

약한 참조를 사용해 순환 참조가 해결되는 것을 확인할 수 있습니다.

 

위에서 약한 참조는 다른 인스턴스의 수명이 짧을 때 사용하며 반드시 변수로 선언된다고 했습니다.

Person 인스턴스(다른 인스턴스)가 할당 해제되면(수명이 짧음) ARC에 의해 tenant에 nil이 들어갈 수 있습니다.(옵셔널, 변수인 이유)

 

unowned reference 미소유 참조

 

약한 참조와 마찬가지로 참조하는 인스턴스를 강하게 참조하지 않습니다.

약한 참조와 다른 점은 다른 인스턴스의 수명이 같거나 수명이 긴 경우 미소유 참조를 사용합니다. 

 

미소유 참조는 항상 값이 있다고 예상합니다.

ARC는 미소유 참조를 nil로 설정하지 않습니다. 그렇기 때문에 미소유 참조는 옵셔널이 아니겠죠.

즉 할당이 해제되지 않은 인스턴스를 참조하는 경우에만 사용해야 합니다.

만약 해당 인스턴스의 할당이 해제된 후 미소유 참조에 의해 참조 값에 접근하려면 런타임 오류가 발생합니다.

 

예를 들어 


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

Customer 클래스와 CreditCard 클래스를 생성했습니다. 

 

고객에게 신용카드가 있거나 없을 수 있지만 신용카드는 항상 고객과 연결됩니다.

그래서 Customer클래스의 card는 CreditCard가 있거나 없을 수 있기 때문에 옵셔널입니다.

신용카드는 항상 고객이 있기 때문에 당연히 Customer인스턴스를 참조할 것 입니다. 그럼 순환 참조가 당연히 생기겠죠.

여기서는 순환 참조를 피하기 위해 미소유 참조로 정의해줍니다.


var john: Customer?

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

john Custormer변수를 정의합니다.

Customer인스턴스를 만들어 인스턴스를 초기화합니다.

CreditCard인스턴스를 생성하고 고객(john)의 card속성으로 할당합니다.

 

두 인스턴스의 참조는 이렇습니다.

 

Customer인스턴스는 강한 참조로 CreditCard 인스턴스를 참조합니다.

CreditCard인스턴스는 미소유 참조로 Customer인스턴스를 참조합니다.

 

 

john의 강한 참조를 끊어줍니다.

 

customer참조는 미소유 참조기 때문에 만약 john변수의 강한 참조를 분리하면 Customer인스턴스에 대한 강한 참조가 없습니다.


john = nil

Customer인스턴스에 대한 강한 참조가 없으므로 할당이 해제됩니다.

CreditCard인스턴스에 대한 강한 참조가 없으므로 이 또한 할당 해제됩니다.

 

John Appleaseed is being deinitialized가 출력되며 함께 Card 1234_5678_9012_3456 is being deinitizlied이 출력됩니다.

 

미소유 참조는 다른 인스턴스의 수명이 같거나 수명이 긴 경우 사용한다고 했습니다.

customer에 미소유 참조로 선언한 이유는 다른 인스턴스(Customer)의 수명이 같았기 때문입니다.

 

반응형

'개발 > Swift' 카테고리의 다른 글

스위프트 배열에서 중복을 제거하는 방법  (0) 2019.05.19
Phantom Type 팬텀타입  (0) 2019.05.07
Swift ARC에 대해서(2)  (0) 2019.04.19
Swift ARC에 대해서(1)  (0) 2019.04.18
Swift String Substring  (0) 2019.04.12