강한 참조 사이클이 발생할 때, 메모리 누수가 생긴다.
두 클래스 객체가 각각 인스턴스로 생성이 되는데, 문제는 서로의 인스턴스를 내부의 프로퍼티에서 가리키고 있을 때 레퍼런스 카운팅이 각각 2가 되고 각 인스턴스에 nil을 할당해줘도 이미 서로를 레퍼런스 카운팅하고 있는 사이클이 존재하기 때문에 레퍼런스 카운팅이 1로 남아있습니다. 이렇게 서로가 서로를 가리키고 있기 때문에 강한 참조 사이클이 일어나고, nil은 할당이 되지만 메모리 해제는 되지 않고 메모리 누수가 일어납니다.
이러한 메모리 누수를 해결하기 위해 약한 참조와 비소유 참조를 사용합니다.
두 참조 모두 가리키는 인스턴스의(상대방의) 레퍼런스 카운팅(RC) 숫자를 올라가지 않게 합니다. 즉 서로를 가리키지만 상대를 약하게 참조하기 때문에 RC의 숫자는 올라가지 않습니다.
때문에 각 인스턴스가 메모리에서 해제되면 서로를 가리키는 것도 해제가 되는 것입니다.
약한 참조(Weak Reference)
약한 참조의 경우 참조하고 있던 인스턴스가 메모리에서 해제되면 자동으로 nil로 초기화 됩니다. 그렇기 때문에 약한 참조로 선언할 경우 반드시 옵셔널 타입으로 선언해야 합니다.
class Person {
var pet: Dog? = nil
}
class Dog {
weak var owner: Person? = nil // 약한 참조! 반드시 옵셔널 타입!
}
var gildong: Person? = Person()
var bori: Dog? = Dog()
gildong.pet = bori
bori.owner = gildong // owner는 Person 타입의 인스턴스를 약하게 참조하고 있기 때문에 RC가 증가하지 않습니다.
gildong = nil
// bori.owner는 nil이 되었습니다.
// 💡 약한 참조의 경우 가리키고 있던 인스턴스가 메모리에서 해제되는 순간 가리키지 않게, 자동으로 메모리에 nil이 할당되게 됩니다.
비소유 참조(Unowned Reference)
비소유 참조의 경우 약한 참조와 달리, 참조하고 있던 인스턴스가 메모리에서 해제되어도 nil로 초기화 되지 않습니다. 그리고 에러가 발생합니다.
class Person {
var pet: Dog? = nil
}
class Dog {
unowned var owner: Person? = nil // 비소유 참조! 옵셔널 타입이 아니어도 됨!
}
var gildong: Person? = Person()
var bori: Dog? = Dog()
gildong.pet = bori
bori.owner = gildong
gildong = nil
// 💡 약한 참조와 달리 bori.owner가 nil이 할당되지 않았기 때문에 bori.owner를 호출하는 순간 에러가 나고 앱이 죽습니다.
소유자에 비해, 즉 현재 객체의 인스턴스보다 생명주기가 짧은 인스턴스를 참조할 경우 약한 참조(weak)를 주로 사용합니다. 생명주기가 더 짧은 인스턴스가 메모리 상으로 해제되어도 그 소유자 인스턴스 상에서 해당 참조가 메모리 상으로 알아서 해제되기 때문입니다.
그러나 소유자에 비해, 즉 현재 객체의 인스턴스보다 생명주기기 긴 인스턴스를 참조할 경우 비소유 참조(unowned)를 사용해도 됩니다. 생명주기가 긴 인스턴스를 참조하고 있기 때문에 소유자보다 먼저 메모리 상으로 해제되지 않을 것이고 에러가 발생할 일이 없기 때문입니다. 그러나 이러한 경우에도 약한 참조(weak)를 사용해도 될 것 같습니다.
강한 참조 사이클로 인한 메모리 누수의 경우, ARC로도 해제가 불가능하기 때문에 주의해야 합니다!