AnyObject와 weak에 대해 알게 되었다. 이 두가지는 결국 Retain Cycle을 피하기 위해 채택하고 작성하는 키워드들인데, Retain Cycle에 대해 조금 더 톺아보기로 하자.Retain Cycle을 이해하기 위해, 먼저 ARC에 대해 알아야 한다. ARC(Automatic Reference Counting)는 스위프트가 대부분의 메모리 관리를 대신 해주는 기능이다.
이건 매우 좋은 소식이다. 우리는 덕분에 클래스와 같이 참조하는 값들을 선언할 때 따로 참조 카운트를 신경쓰지 않고 코딩을 할 수 있다.(일단은.. 우리가 지금까지 하던 프로젝트에 한해서는 그랬다.)
ARC의 원리 자체는 간단하다. 기본적으로 클래스의 객체를 포인팅하는 각각의 참조는 강한참조 이다. 최소한 하나의 강한참조가 있다면, 이 객체의 메모리는 해제되지 않는다. 그리고 강한참조가 하나도 없다면, 이 객체는 메모리에서 자동으로 해제될 것이다.
당연히~ 아니니까 이 공부를 하는 것이다. 사실, ARC는 정말 일을 잘 하고 있고, 지금까지는 괜찮았다. 하지만 조금씩 예외가 생기고 있다.(벌써 Delegate만 봐도 그렇다.)
ARC가 만능은 아니기 때문에, 우리는 특정 상황에 약간의 도움을 줘야 한다.
class Wonbi {
var wonbi: Wonbi?
init() {
print("나.. 메모리에 등.장.")
}
deinit {
print("나.. 메모리에서 퇴.장.")
}
}
var person1: Wonbi? = Wonbi() // "나.. 메모리에 등.장."이 프린트 됨.
var person2: Wonbi? = Wonbi() // "나.. 메모리에 등.장."이 프린트 됨.
person1?.wonbi = person2
person2?.wonbi = person1
여기 Wonbi라는 중2병 걸린 것 같은 타입이 있다. 이 타입은 wonbi라는 자기자신 타입의 프로퍼티와, 초기화 될 때랑 메모리에서 해제될 때 이상한 말을 내뱉는 이상한 타입이다. (실제로 나는 이런 사람 아니다.)
그리고, 놀랍게도 person1과 person2는 Wonbi타입의 인스턴스이다. 초기화까지 잘 되었다. 초기화 되자마자 바로 중2병 스러운 멘트까지 치고 있다. 정말 놀라워!
그다음, 각 인스턴스들(person1과 person2)이 wonbi라는 프로퍼티를 이용해 서로를 참조 하도록 해주었다.

위 상황을 그림으로 한번 표현해보았다. 아 참고로 이거 그리는 사이트는 여기다. 캠퍼 미니가 알려줬다. 압도적 감사!
그리고보니 뭔가.. 뭔가 일어나고 있다. 클래스는 참조타입이기에 인스턴스화를 하면 값을 참조 한다. 그림처럼! 자 다음에는 이렇게 해보자.
person1 = nil
person2 = nil
person1과 person2는 이제 nil을 할당 받음으로써 참조가 사라졌다. 그럼 메모리에서 해제되어야 한다. 바로 중2병 스러운 멘트를 또 뱉어낼 것이다.
근데 실행해보면 안 뱉는다. 못믿겠다고? 직접 보여주겠다.


현재 상황이 이런 상태이기 때문이다. 맨 처음 ARC를 보면, "최소한 하나의 강한참조가 있다면, 이 객체의 메모리는 해제되지 않는다." 라고 말하고있다. 지금 보면 딱! 강한참조가 서로에게 하나씩 있다. 그래서 이 인스턴스들은 메모리에서 해제되지 않는다.
게다가, 우리는 더이상 코드를 통해 이 인스턴스에 접근할 수 없다. person1과 person2에 nil을 할당하면서 각 인스턴스의 참조를 없애버리고 말았다.
이 상황이 우리가 만든 앱 어딘가에 들어있다면.. 그리고 그게 여러개라면..?
for _ in 0...10000 {
var person1: Wonbi? = Wonbi()
var person2: Wonbi? = Wonbi()
person1?.wonbi = person2
person2?.wonbi = person1
person1 = nil
person2 = nil
}
다 방법이 있다. 여기서 등장하는 것이 바로 약한참조 (weak reference) 이다. 이 약한참조의 사용은 Retain Cycle을 피하는 방법 중 하나이다.
우리가 참조를 weak참조로 선언한다면, 이는 강한참조가 아닌 약한참조가 된다. 약한참조가 되면, 스위프트는 이 참조를 강한참조로 보지 않기 때문에 이를 제외하고 참조를 계산하게 된다.
class Wonbi {
weak var wonbi: Wonbi? // 이제 이 프로퍼티는 약한 참조가 된다.
init() {
print("나.. 메모리에 등.장.")
}
deinit {
print("나.. 메모리에서 퇴.장.")
}
}
var person1: Wonbi? = Wonbi()
var person2: Wonbi? = Wonbi()
person1?.wonbi = person2
person2?.wonbi = person1

다시한번 미니에게 감사를...
그럼 이제 nil을 할당해보자.
person1 = nil
person2 = nil


중2병 멘트를 뱉어내며 메모리에서 산화되는 두 인간을 볼 수 있다.
이로써 우리는 weak키워드를 통해 약한참조를 지정해줌으로써 Retain Cycle을 피할 수 있게 되었다.
매우 중요한 부분이 하나 있는데, 인스턴스를 메모리에서 해제하고 나면, 해당 변수에 대한 응답이 nil이 된다.(위 예제에서 인스턴스 안에 있는 프로퍼티가 nil이 된다는 의미)
만약 nil이 아니라면 이미 메모리상에서 해제된 객체의 영역을 변수가 참조하는 것이 되버리므로 런타임 에러가 발생하게 된다.
따라서 약한참조의 변수는 nil값을 언젠가 할당 받을 가능성이 분명하기 때문에 반드시 옵셔널 타입이어야만 한다.
Automatic Reference Counting
Retain Cycles, Weak and Unowned in Swift
[Swift] weak, unowned, Retain cycle 톺아보기