순환 참조의 개념에 대해 간단히 짚어보자.
두 인스턴스가 서로가 서로를 참조하고 있는 상황.
따라서 하나의 인스턴스를 참조하는 변수가 nil
이되어서 메모리해제가 되어야하는 시점에서도
인스턴스를 참조하고 있는 인스턴스가 있어서 reference count는 여전히 1기때문에
메모리 해재가 안되어서 메모리 누수가 발생한다.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
Person
, Apartment
두개의 클래스를 구현해주었다.
각각은 name
, unit
이라는 String타입의 프로퍼티가 있고,
서로의 타입 값을 변수로 가진다. var apartment: Apartment?
, var tenant: Person?
그리고 deinit( )
메서드에서는 print문을 찍어주어 dealloacate가 되는것을 확인 할 수 있도록 했다.
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed") // reference count +1
unit4A = Apartment(unit: "4A") // reference count +1
john
과, unit4A
에 각각 Person인스턴스와 Apartment인스턴스를 할당한다.
그럼으로써 ARC는 각 인스턴스의 reference count를 1씩 올려준다.
john!.apartment = unit4A // Apartment인스턴스 reference count +1 (총2)
unit4A!.tenant = john // Person인스턴스 reference count +1 (총2)
Person인스턴스의 apartment
프로퍼티에 Apartment인스턴스를 할당해줌으로써
➡️ Apartment 인스턴스의 reference count가 1이 증가하고
Apartment인스턴스의 tenant
프로퍼티에 Person인스턴스를 할당해줌으로써
➡️ Person 인스턴스의 reference count가 1이 증가한다.
john = nil //Person인스턴스 reference count -1 (총1)
unit4A = nil //Apartment인스턴스 reference count -1 (총1)
Person 인스턴스를 가리키는 john
과 Apartment 인스턴스를 가리키는 unit4A
를 nil로 해제해줌으로써 두 인스턴스의 reference count는 1씩 줄어든다.
여기서 문제가 발생한다.
여전히 서로가 강한 참조를 유지하고 있기 때문에
reference count가 1이기 때문에 ARC에 의해 메모리 해제가 되지 않는다.
만약 둘 중 하나만 참조를 하고 있다면 변수가 nil이 되면서 두 인스턴스 모두 해제 될것이다.
예를 들어 Apartment인스턴스만 참조되고 있다면
john!.apartment = unit4A // Apartment인스턴스 reference count +1 (총2)
두 변수에 nil을 할당하면
john = nil //Person인스턴스 reference count -1 (총0)
// "john is being deinitialized"
unit4A = nil //Apartment인스턴스 reference count -2 (총0)
// "unit4A is being deinitialized"
Person 인스턴스가 먼저 해제되고,
그다음 Apartment 인스턴스가 해제될 것이다.
ARC는 인스턴스의 reference count를 세고, reference count가 0이되면 자동으로 인스턴스를 deallocate를 해주는 친구인데, 강한 순환 참조의 인스턴스는 deallocate을 못한다.
강한 순환 참조는 두 인스턴스가 refernce count가 1이기 때문에 ARC는 두 인스턴스를 메모리에 남겨둔다. 따라서 메모리 누수가 발생한다. ARC는 "아! 저 친구들은 강한 순환 참조여서 reference count가 1이구나! 그러니까 해제해줘야지~"까지 판단을 하지 못하는 것이다.
ARC는 Refernece Count만 (완벽하게) 자동으로 세주고 일정 부분 메모리 해제를 자동으로 해주긴 하지만,
결국 메모리에 관한 관리는 프로그래머가 챙겨줘야하는 부분인 것이다.
해결책!
- 강한 순환 참조를 끊어준다
- 따라서
weak
,unowned
참조라는 것들이 있다.