본문 바로가기

iOS

iOS - ARC, Weak, Unowned

 

[소유 정책]

 

1. 모든 객체는 생성될 때 참조 카운트가 1이 된다.

 

2. retain 메세지를 보내면 참조 카운트가 1 증가한다. 이 메시지를 보낸 호출자는 객체를 '소유' 한다고 한다.

 

3. release 메시지를 보내면 참조 카운트가 1 감소한다. 이 메시지를 보낸 호출자는 객체의 소유권을 포기한다.

 

4. autorelease 메시지를 보내면 현재 사용 중인 오토릴리즈 풀 블록의 실행이 종료되는 시점에 참조 카운트가 1이 감소한다.

 

5. 참조 카운트가 0이 되면 객체의 메모리가 해제된다.

 

[오토릴리즈 풀]

 

오토릴리즈 풀은 autorelease 메시지를 받은 객체가 해지되기 전까지 저장되는 공간이다.

 

이 공간에 저장된 객체들은 오토릴리즈 풀이 해제될 때 다함께 release된다.

 

각 스레드 하나에 오토릴리즈 풀 스택이 있고, autorelease 메시지를 받은 객체가 자신의 스레드 중 최상위 풀에 소유된다.

 

[강한 참조와 문제]

 

강한 참조는 우리가 흔히 쓰는 참조이다.

 

class A{

    var b : B?

    deinit {

        print("A is deinit")

    }

}

 

class B{

    var a : A?

    deinit {

        print("B is deinit")

    }

}

 

var a : A? = A()

var b : B? = B()

 

a?.b=b

b?.a=a

 

a = nil

b = nil

cs

 

A 클래스의 객체 a 와 B 클래스의 객체 b 는 서로를 참조하고 있다.

 

a 가 가르키고 있는 A 객체는 참조 카운트가 2이다. 왜냐면 a 가 참조하고 있고, b 안에 있는 프로퍼티 a 가 참조하고 있기 때문이다.

b 가 가르키고 있는 B 객체도 2이다.

 

a 와 b 를 둘 다 nil로 바꿔주고 이제 더 이상 참조를 할 방법이 없지만, 메모리가 해제되지 않았다.

 

참조 카운트가 1개씩 남아있기 때문이다.

 

이런 상황은 메모리 누수이고 피해야할 상황이다.

 

이 것이 강한 참조의 문제이다.

 

[약한 참조]

 

약한 참조는 weak 키워드를 변수에 붙여서 약한 참조로 만드는 것이다. 이는 무조건 Optional 자료형에만 사용 가능하다.

 

class A{

    var b : B?

    deinit {

        print("A is deinit")

    }

}

 

class B{

    weak var a : A?

    deinit {

        print("B is deinit")

    }

}

 

var a : A? = A()

var b : B? = B()

 

a?.b=b

b?.a=a

 

a = nil

b = nil

//A is deinit

//B is deinit

cs

 

B 의 객체 안에 속성 a 를 weak 키워드를 붙여 약한 참조로 만들어줬을 뿐인데 메모리가 정상적으로 해제되었다.

 

원리는 다음과 같다.

 

B 객체의 속성 a 는 A 객체를 약한 참조하기 때문에 A 객체는 참조 카운트가 1이다. B 객체는 참조 카운트가 2이다.

 

A 객체를 nil 을 할당하여 해제하였으므로 A 객체는 참조 카운트가 0이 되어 해제되고, 해제되는 과정에서 A 객체 내의 속성 b 는 B 객체에 대한 참조를 포기한다.

 

그 결과, B 객체도 참조 카운트가 1이 된다.

 

그리고 B 객체도 nil 을 할당하여 해제하니 모두 해제가 완료되었다.

 

[비소유 참조]

 

unowned 라는 키워드를 속성 선언 앞에 붙여서 선언할 수 있는데, 이는 약한 참조와 반대로 Optional 자료형이 아닌 값만을 가질 수 있다.

 

참조 대상에 대한 강한 참조를 유지하지만 항상 유효한 대상을 참조한다고 확신하는 것이다. 

 

이는 비소유 참조이고, 참조 사이클을 형성하지 않고 해제된다.

 

class A{

    var b : B?

    deinit {

        print("A is deinit")

    }

}

 

class B{

    unowned var a : A

    

    init(a : A){

        self.a=a

    }

    

    deinit {

        print("B is deinit")

    }

}

 

var a : A? = A()

var b : B? = B(a : a!)

 

a?.b=b

 

a = nil

b = nil

//A is deinit

//B is deinit

cs



[클로저의 강한 참조 사이클]

 

클로저도 참조 형식이다.

 

예를 들어, 클래스 내에 하나의 속성이 클로저를 참조하고 있다고 해보자.

 

그 클로저 내부에서 self 라는 키워드를 사용하면 클로저는 자기 자신객체를 참조하게 되므로 참조 사이클이 형성된다.

 

그럴 땐 클로저 캡쳐 목록을 이용해 해결할 수 있다.

 

클로저 캡쳐 목록은 클로저 중괄호 안의 대괄호 [] 사이에 weak 혹은 unowned 키워드를 써주고 캡쳐대상들을 쉼표로 분리해서 나열해준다.

 

{

[weak 캡쳐 대상들] (파라미터 목록) -> 리턴형 in

클로저에서 실행할 코드

}

 

class A{

    var a : Int = 0

    

    lazy var closure = {

        [unowned self] () in

        print(self.a)

    }

    

    deinit {

        print("A is deinit")

    }

}

 

var a : A? = A()

 

a?.closure()

 

a = nil

//0

//A is deinit

cs

 

이 스니펫을 보면, closure 라는 클로저를 담는 속성을 lazy로 선언해준 이유는 자기 자신의 초기화가 완료되지 않고 self 란 값을 쓸 수 없기 때문이다.

 

그리고 self 란 값을 그냥 써주면 강한 참조가 발생하여 A 객체가 제대로 해제되지 않지만,

 

클로저의 캡쳐 목록을 사용했기 때문에 제대로 해지가 된 모습이다.

 

'iOS' 카테고리의 다른 글

iOS - Closure 최적화  (0) 2020.04.30
iOS - JSON Parsing  (0) 2020.04.27
iOS - Cocoa Pods  (0) 2020.04.09
iOS - Custom Fonts  (0) 2020.04.01
iOS - ImagePicker (이미지 피커)  (0) 2020.03.27