천원의 개발

Swift ARC(Automatic Reference Counting) 메모리 참조 방식[Weak, Strong, Unowned] 본문

iOS&Swift🍎/Swift

Swift ARC(Automatic Reference Counting) 메모리 참조 방식[Weak, Strong, Unowned]

천 원 2022. 7. 7. 20:19

ARC(Automatic Reference Counting) 는

 - 컴파일시 코드를 분석해서 자동으로  참조 카운터를 증가, 감소 시켜주는 것.

 - 더 이상 참조되지 않는 인스턴스를 메모리에서 해재해 주는 것

 

ARC가 등장하기 전에는 MRR(Manual Retain Release)를 통해 개발자가 직접 참조 카운터를 관리 했습니다.

 

strong (강한 참조)

• 해당 객체의 소유권을 가지며 자신이 참조하는 객체의 참조 카운터를 증가 시킨다

• 값을 지정하는 시점에 참조 카운터를 증가시키고 참조가 종료되면 참조 카운터를 감소시킨다

• 선언시 아무것도 적어주지 않으면 strong이 된다.

class Human{
    var name: String!
    var age: Int!
    
    init(name: String, age: Int){
        self.name = name
        self.age = age
    }
}

var yoonjae : Human? = Human(name: "younjae", age: 23) // 객체 생성시 참조 카운터 1증가
yoonjae = nil // 객체가 사라져 참조 카운터 1감소 해서 0이 되면서 메모리 해제되는것

강한 참조하는 경우 문제가 되는 경우가 있는데 강한 순화 참조(String Refernce Cycles)가 만들어 질 수있습니다. 이 경우 참조 카운터 가 감소하지 못해 메모리 누수가 발생합니다. 코드를 통해 강한 순환 참조를 살펴봅시다.

 

class Jack {
    var strong: Jack? = nil //강한 참조 객체
}

//두개의 객체 변수 선언
var strong1: Jack? = Jack()  //객체 변수
var strong2: Jack? = Jack()  //객체 변수

//서로 강한 참조 (강한 순환 참조)
strong1?.strong = strong2
strong2?.strong = strong1

//두개의 객체 변수 메모리 해제
strong1 = nil
strong2 = nil

마지막 두개의 객체 변수 메모리는 nil을 넣어 해제가 되었지만 strong은 nil을 넣어주지도 못하여 메모리 누수가 일어나고 있습니다.

 

이러한 강한 순환 참조(Strong Reference Cycle)를 해결하는 방법에는 두가지가 있습니다. 두 가지 방법으로 약한 참조(weak reference)와 미소유 참조(unowned reference)입니다.

 

weak (약한 참조)

해당 인스턴스의 소유권을 가지지 않고, 주소값만을 가집니다.(포인터 개념)

자신이 참조하는 인스턴스의 참조 카운터를 증가시키지 않습니다.

weak var yoonjae : Human? = Human(name: "younjae", age: 23) // 개채 생성과 동시에 해제
print(yoonjae) // nil 출력

자 그럼 어떻게 weak를 사용하여 순환 참조를 해결하는지 코드를 통해 알아 봅시다.

class Jack {
    weak var strong: Jack? = nil //강한 참조 객체
}

//두개의 객체 변수 선언
var strong1: Jack? = Jack()  //객체 변수
var strong2: Jack? = Jack()  //객체 변수

//서로 강한 참조 (강한 순환 참조)
strong1?.strong = strong2
strong2?.strong = strong1

//두개의 객체 변수 메모리 해제
strong1 = nil
strong2 = nil

약한 참조를 하기 위해서 변수의 앞에 weak를 붙여줍니다.

두 개의 객체 변수의 메모리가 해제되었을 때 강한 참조 일때와 달리 약한 참조를 한 객체는 ARC에서 자동으로 객체의 메모리까지 해제를 시켜준다는 점입니다. 그렇게 된다면 메모리 누수문제를 해결할 수 있습니다.

 

unowned (미소유 참조)

 해당 인스턴스의 소유권을 가지지 않습니다.

 자신이 참조하는 인스턴스의 참조 카운터를 증가시키지 않습니다.

• nil이 될 수 없습니다. 옵셔널로 선언 되어서는 안됩니다.

unowned var yoonjae : Human = Human(name: "younjae", age: 23) // 객체 생성과 동시에 해제
print(yoonjae) // 오류 발생

위의 코드와 설명을 보면 weak 참조랑 다른게 없는거 같은데 왜 nil이 아니라 오류가 발생할까?

 

 weak는 객체를 계속 추적하면서 객체가 사라지면 nil로 변경 되지만, unowned는 객체가 사라지게 되면 *댕글링 포인터가 남는다.

    이 댕글링 포인터를 참조하게 되면 crash가 나게 되는 것이다. 

 

*댕글링 포인터(Dangling pointer): 원래 바라보던 객체가 해제되면서 할당되지 않는 공간을 바라보는 포인터

 

어떤 상황에 쓰이는가?

• strong : 참조 카운터를 증가시켜 ARC로 인한 메모리 해제를 피하고, 객체를 안전하게 사용하고자 할 때 사용.

• weak : 순환 참조에 의해 메모리 누수 문제를 막기 위해 사용

• unowned: 객체의 life cycle이 명확하고 개발자에 의해 제어 가능이 명확한 경우, weak Optional 타입 대신하여 사용