본문 바로가기
iOS/Swift

Swift) weak, unowned

by Jiseong 2022. 2. 13.

weak

이전 글에서 다룬 weak에 대해 새로이 안게 있어서..

class Person {
    var name: String
    weak var puppy: Puppy?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Person deinit")
    }
}

class Puppy {
    var name: String
    var owner: Person?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Puppy deinit")
    }
}

var john: Person? = Person(name: "John")
var jackson: Puppy? = Puppy(name: "Jackson")

john?.puppy = jackson
jackson?.owner = john

//john = nil
jackson = nil
john = nil

위 코드로 예를 들어 설명하였는데 다시 간략히 설명하자면 어떤 클래스 인스턴스를 참조하는 변수가 weak로 선언된다면 해당 클래스 인스턴스는 RC를 카운트하지 않는다.

 

근데 여기서.. 궁금한 건 위 코드로 예를 들면, Person과 Puppy를 참조하고 있는 과 잭슨중 누구를 weak를 선언해주든 상관없을까? 둘중에 하나만 해줘도 강한 순환 참조를 해제할 수 있는데..

 

이렇게 weak로 선언해줄 프로퍼티를 결정하는 기준은 각 프로퍼티 중 수명이 더 짧은 인스턴스를 가리키는 프로퍼티를 약한 참조로 선언하면 된다.

잭슨(Puppy) = nil -> 존.puppy = nil = weak 존.puppy 
잭슨(Puppy)이 더 빨리 죽으므로 잭슨을 가리키는 존의 puppy 프로퍼티를 weak로

존(Person) = nil -> 잭슨.owner = nil = weak 잭슨.owner
존(Person)이 더 빨리 죽으므로 존을 가리키는 잭슨의 owner 프로퍼티를 weak로

unowned

unowned는 weak와... 비슷..해 보인다

.

weak와 동일하게 RC를 카운트하지않고, 그 덕에 강한 참조 순환 문제를 해결할 수 있다.

 

weak와 다른 점은 weak는 참조 대상(클래스 인스턴스)를 트래킹하여 해당 클래스 인스턴스가 deinit될 때 nil이 할당되지만, unowned는 참조 대상을 트래킹하지 않으며 deinit될 때 nil을 할당하지 않고 클래스 인스턴스가 해제된 메모리 주소값을 계속 들고 있는다.

 

이미 해제된 메모리 주소값을 들고있는 프로퍼티에 접근하면 당연히 프로그램이 뻑이 나게된다.

여기서 알 수 있는 것은 unowned 선언을 한 프로퍼티가 참조하고 있는 클래스 인스턴스는 해당 프로퍼티를 갖고있는 클래스 인스턴스보다 먼저 해제되지 않아야됨을 알 수 있다.

 

먼저 해제되면 프로퍼티에 접근하는 순간, 별 이상한 주소 가르킨다고 뻑이나버리니깐!!!!!!!!!!

예제를 보자

class Person {
    var name: String
    unowned var puppy: Puppy?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Person deinit")
    }
}

class Puppy {
    var name: String
    var owner: Person?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Puppy deinit")
    }
}

var john: Person? = Person(name: "John")
var jackson: Puppy? = Puppy(name: "Jackson")

john?.puppy = jackson
jackson?.owner = john

아 puppy 자꾸 자동완성되냐 왜!!!!!!!!!!! 대문자 아니라니깐

 

전체적인 흐름은 이전글에서 공부한 weak와 동일함을 볼 수 있다.

 

다른 점을 보자면..

jackson = nil

위에서 말한것을 토대로 코드를 보면 John(Person)의 프로퍼티 puppy가 가르키는 Jackson이 먼저 해제되면 안되는데 해제를 한다면?

 

여기서 weak와 unowned 의 다른 점을 알 수 있다.

아... 화살표 색깔 바꼈네... 하늘색을 weak로 할랬는데..

 

Puppy 클래스 인스턴스 RC가 0이 되었으니 ARC가 메모리에서 해제시킨다.

여까진 같다.

 

하지만

weak로 선언된 프로퍼티가 참조하는 클래스 인스턴스가 해제됐을 때 해당 프로퍼티에 nil이 할당된 반면

unowned로 선언된 프로퍼티가 참조하는 클래스 인스턴스가 해제 됐을 땐 해당 메모리 주소에서 클래스 인스턴스가 해제되어 아무것도 존재하지 않는데도 불구하고 계속 그 주소를 갖고있는다.

 

이를 댕글링 포인터라고 한다.

이 상태에서 한 클래스 인스턴스를 참조하고 있던(현재 빈 주소 값을 갖고있는) 프로퍼티를 호출하면 Crash가 발생한다.

unowned의 의도대로 실행한다면?

 

unowned로 선언된 프로퍼티가 가르키는 클래스 인스턴스가 먼저 해제되면 안되므로

john = nil
jackson = nil

이런식으로 해줘야겠지?

 

weak와 반대로 unowned로 선언해줄 프로퍼티를 결정하는 기준은 각 프로퍼티 중 수명이 더 긴 인스턴스를 가리키는 프로퍼티미소유 참조로 선언하면 된다.

잭슨(Puppy) = nil -> 잭슨.owner가 수명이 더 길다 = unowned 잭슨.owner

존(Person) = nil -> 존.puppy 가 수명이 더 길다 = unowned 존.puppy

하지만 nil을 할당하는 weak와 달리 댕글링 포인터가 발생해 프로그램이 즉사할 수 있는 위험이 있는 unowned는 unowned로 선언된 프로퍼티가 가르키는 클래스 인스턴스가 절대로 해당 프로퍼티를 갖고있는 클래스 인스턴스보다 먼저 죽을 일이 없는 것이 확실할 때 사용해야 한다.

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

SOLID  (0) 2022.02.13
Swift) App LifeCycle  (0) 2022.02.13
Swift) 순환 참조 , strong, weak  (0) 2022.02.13
Swift) ARC  (0) 2022.02.13
Swift) Protocol(1)  (0) 2022.02.13

댓글