Swift

5. iOS 강한참조(strong), 약한 참조(weak), 미소유 참조(unowned

Daesiker 2022. 7. 28. 17:37
반응형

5-1. 개요

저번 포스팅에서는 순환참조가 왜 일어나는지 포스팅을 했었는데 이번에는 순환참조가 일어나지 않게 할려면 어떻게 해야하는지에 대해 포스팅할 예정이다. Swift에서 Reference Type을 선언할 때 변수인지 상수인지를 결정하는 let과 var이란 키워드가 있는건 모두가 알고 있지만 이 키워드 앞에도 어떻게 참조할 것인지에 대해 정의해주는 키워드가 존재한다. 바로 strong과 weak와 unowned이다. 총 3가지인데 오늘은 참조 키워드에 대해 포스팅할 예정이다.


5-2. Strong(강한 참조)

개요에서 총 3개의 키워드가 있다고 했지만 swift에 strong이라는 키워드는 없다. 즉, 아무 키워드도 사용하지 않으면 강한 참조로 인식한다는 이야기이다. 강한 참조로 Reference Type을 선언하거나 대입연산을 하게 된다면, 지금까지 공부해왔던 ARC 관리처럼 RC 카운터가 1 증가 한다.

class Human {
    var age:Int
    var name:String

    init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
        print("\(name) init")
    }

    deinit {
        print("\(name) deinit")
    }

}

var human1:Human? = Human(28, "Daesik") // ARC : 1
print(human1)
var human2 = human1 // ARC : 2

human1 = nil //ARC : 1
human2 = nil //ARC : 0

2022.07.12 - [Swift] - 3. iOS 메모리 관리(ARC, MRC)

 

3. iOS 메모리 관리(ARC, MRC)

1. 개요 iOS의 메모리 영역 중 힙영역을 관리하는 방법은 2가지가 있다. ARC와 MRC란 기법이 있는데 Objective-C에서는 프로젝트 설정을 통해 MRC나 ARC 중 선택하여 사용할 수 있고 Swift에서는 ARC만 사용

daesiker.tistory.com


5-3. Weak(약한 참조)

약한 참조는 순환참조를 해결하기 위해 만들어진 참조 방식으로 인스턴스를 참조할 때 RC가 증가하지 않는다. 참조하던 인스턴스가 메모리에서 해제되는 경우, 즉 RC가 0이 되는 경우 자동으로 nil이 할당되기 때문에 같이 자연스럽게 메모리에서 해제된다. 메모리에서 해제될 때 nil로 바뀌기 때문에 let 키워드로 선언할 수 없고, 옵셔널 타입으로만 선언할 수 있다.

-SourceCode

class Man {
    var name:String
    weak var woman:Woman?

    init(_ name: String) {
        self.name = name
        print("\(name) init")
    }

    deinit {
        print("\(name) deinit")
    }

}

class Woman {
    var name:String
    var man: Man?

    init(_ name: String) {
        self.name = name
        print("\(name) init")
    }

    deinit {
        print("\(name) deinit")
    }

}

var boy:Man? = Man("daesiker")
var girl:Woman? = Woman("dlwlrma")

boy?.woman = girl
girl?.man = boy

boy = nil
girl = nil

-Console

daesiker init
dlwlrma init
dlwlrma deinit
daesiker deinit

이전 포스팅에서 weak 키워드를 쓰지 않고 코드를 실행하면 정상적으로 메모리 해제가 안된 것을 확인할 수 있었는데 Man class에 woman 멤버를 선언 방식을 weak로 변경해서 다시 실행 하니 정상적으로 메모리가 해제된 걸 알 수 있었다.

Step1

Man 인스턴스인 boy와 Woman 인스턴스인 girl을 선언 하였다. 이 때 힙영역과 스택 영역에 데이터가 할당 되고 RC 카운트는 각각 1이 된다.

var boy:Man? = Man("daesiker")
var girl:Woman? = Woman("dlwlrma")

Step2

서로 다른 객체를 순환 참조하였다. 하지만 Man Class의 멤버 woman은 약한 참조로 선언이 되어 있어서, 참조를 하였지만 Woman Instance의 RC는 증가하지 않고 1이 유지된다.

boy?.woman = girl
girl?.man = boy

Step3

boy를 nil로 초기화하면 RC가 1 감소하지만 RC가 0이 아니기 때문에 여전히 살아있다.

boy = nil

Step4

지역변수 girl을 nil로 초기화를 하면 Woman 인스턴스의 RC가 1 감소하여 0이된다. 0이 되면서 Woman Instance도 메모리에서 해제가 되고 해제가 되면 Man 인스턴스의 woman 멤버도 nil로 초기화 되면서 Man 인스턴스의 RC도 1이 감소되면서, 힙영역에 모든 메모리가 해제된다. 출력 결과를 보면 Woman 인스턴스가 Man 인스턴스보다 먼저 해제된걸 알 수 있다.

girl = nil


5-4. unowned 미소유 참조

미소유 참조는 약한 참조와 비슷한 참조 방식이지만 조금 더 디테일하게 작동한다. 순한 참조를 막아주고 참조를 해도 RC 값이 증가하지 않는다. weak와의 차이점은 해제 시점을 잘 생각하고 선언을 해야하는 것이다. 클래스 안에 unowned로 선언된 멤버들은 클래스가 메모리가 해제되기 전에 해제되면 오버 플로우가 발생한다. 즉 해제를 할 때 클래스 인스턴스를 먼저 해제를 하고 그 다음에 unowned 멤버를 해제해야한다.

-SourceCode

class Man {
    var name:String
    unowned var woman:Woman?

    init(_ name: String) {
        self.name = name
        print("\\(name) init")
    }

    deinit {
        print("\\(name) deinit")
    }

}

class Woman {
    var name:String
    var man: Man?

    init(_ name: String) {
        self.name = name
        print("\\(name) init")
    }

    deinit {
        print("\\(name) deinit")
    }

}

var boy:Man? = Man("daesiker")
var girl:Woman? = Woman("dlwlrma")

boy?.woman = girl
girl?.man = boy

girl = nil
print(boy?.woman)

-Console

daesiker init
dlwlrma init
dlwlrma deinit
Fatal error: Attempted to read an unowned reference but object 0x15081ffb0 was already deallocatedPlease submit a bug report (<https://swift.org/contributing/#reporting-bugs>) and include the project and the crash backtrace.

이전에 코드에서 weak로 선언한 woman 멤버를 unowned로 바꾸고 boy를 해제하기 전에 girl을 해제하고 boy에 선언된 woman을 출력해 보았다. girl을 nil로 초기화했기 때문에 출력 결과가 nil이 나와야 정상이지만 오버플로우가 발생한걸 볼 수 있다.

unowned는 이렇게 순서에 따라 오버플로우가 발생하고 앱이 다운될 수 있는 경우가 있기 때문에 신중하게 사용하는 것을 권장한다.

 

반응형