Automatic Reference Counting
이름에도 유추할수 있듯 레퍼런스(참조)를 자동으로 처리해주는 기술.
참조타입 (클래스 등)의 실질적인 데이터는 힙 영역에 저장되고, 인스턴스를 생성할 경우 힙 영역에 있는 데이터가 저장된 주소를 스택 영역에 기록하게 된다. 이를 쉽게 관리하기 위해 ARC라는 기술이 도입이 되었다.
objc시절에는 MRC(Manual Reference Counting)라고, 수동으로 관리하였다.
MRC -> ARC로 넘어오게 되었는데, MRC 코드까지 볼 필요는 없을 것 같고, retain과, release 두가지를 짚고 넘어가자면,
컴파일 시점에 컴파일러가 retain과 release의 코드를 적절하게 알아서 삽입을 해주어서, 런타임 때 정상적으로 retain과 release가 작동하는것.
참조타입의 실질적인 데이터는 힙 영역에 있다고 했었는데, 그럼 이 데이터들을 어떻게 개발자가 관리를 하느냐? Retain Count를 통해서 관리를 해줄 수 있다.
힙 영역을 가르켜서(인스턴스를 만들었을 때) 스택 영역에 쌓이게 되면, Retain Count가 1 증가하여 힙 영역에 기록된다. 이것이 retain이고, 스택에서 pop되거나, nil로 할당하는 등 사용하지 않게 되면 Retain Count가 1 감소하여 힙 영역에 기록된다.
이때, 총 Retain Count가 0이 되면 Class의 소멸자(deinit)이 실행되어 소멸되게 된다.
Class A, Class B를 각각 만들어, 서로를 참조한다는 가정을 해보자.
class ClassA {
var b: ClassB?
init() { print("ClassA init") }
deinit { print("ClassA deinit") }
}
class ClassB {
var a: ClassA?
init() { print("ClassB init") }
deinit { print("ClassB deinit") }
}
이 코드를 기반으로 아래 내용들을 정리해보자면,
class ClassA {
var b: ClassB?
init() { print("ClassA init") }
deinit { print("ClassA deinit") }
}
class ClassB {
weak var a: ClassA?
init() { print("ClassB init") }
deinit { print("ClassB deinit") }
}
var classA: ClassA? = ClassA() // ClassA Count + 1 (Strong)
var classB: ClassB? = ClassB() // ClassB Count + 1 (Strong)
classA.b = classB // ClassB Count + 1 (Strong)
classB.a = classA // ClassA Count Not add (Weak)
classA = nil // A는 count가 1이였고, nil을 할당함으로써 0이 되어 deinit.
print(classB.a) // nil (weak로 설정했기에 자동으로 nil이 되었다.)
weak는 print(classB.a)의 사례처럼 본인의 의도와 관계 없이 nil이 될 수 있기에 무조건 옵셔널 타입으로 선언해야 한다.
class ClassA {
var b: ClassB?
init() { print("ClassA init") }
deinit { print("ClassA deinit") }
}
class ClassB {
unowned var owner: ClassA
init(owner: ClassA) {
self.owner = owner
print("ClassB init")
}
deinit { print("ClassB deinit") }
}
var classA: ClassA? = ClassA() // ClassA Count + 1 (Strong)
if let classA {
var classB: ClassB? = ClassB(owner: classA) // ClassB Count + 1 (unowned), ClassA Count + 1 (strong)
}
classA?.b = classB // ClassB Count + 1 (strong)
classA = nil // ClassA Count - 1
classB = nil // ClassB Count - 1
if let애서 classA가 있는지 없는지 확인하고 넣었다. classB의 owner는 classA의 데이터가 실제로 있지 않으면 사용할 수 없기 때문.
2개 이상의 클래스가 서로가 서로를 참조할때, Retain Count가 0이 되지 않아 메모리에서 해제 되지 않는 상황을 의미한다.
순서가 바뀐거같은데, Strong하게 서로를 참조하게 되면, 불사의 상태가 되어버린다.
class ClassA {
var b: ClassB?
init() { print("ClassA init") }
deinit { print("ClassA deinit") }
}
class ClassB {
var a: ClassA?
init() { print("ClassB init") }
deinit { print("ClassB deinit") }
}
var classA: ClassA? = ClassA() // ClassA Count + 1 (Strong)
var classB: ClassB? = ClassB() // ClassB Count + 1 (Strong)
classA.b = classB // ClassB Count + 1 (Strong)
classB.a = classA // ClassA Count + 1 (Strong)
classA = nil // ClassA Count - 1
classB = nil // ClassB Count - 1
이렇게 되버리면, Retaion Count 1씩 남아버렸고, 더이상 프로그래머가 Retain Count를 내릴 수 없는 상태가 되었음으로, classA와 B는 불사신이 되어버렸다.