: 메모리의 힙 영역에 할당된 데이터는 명시적으로 관리해야만 메모리에서 해제가 됩니다. 이를 위해 참조 횟수(Reference Count, RC)를 세어 메모리 관리를 수행하며, 런타임 시 참조 횟수를 기반으로 메모리 해제 여부를 결정합니다. 만약 메모리에서 해제되지 않으면 메모리 누수 현상이 발생할 수 있습니다.
☑️ 메모리 누수현상이란??
: 힙에 올라간 데이터들이 사라지지 않는 현상 & 사라지게 할 수 있는 방법이 없는 상황
: ARC는 특정 클래스 객체를 참조하는 변수, 상수, 프로퍼티들의 참조 횟수가 0이 되는 시점에 자동으로 힙에서 메모리를 해제합니다. 클래스 인스턴스가 메모리에서 해제될 때 즉시 deinit 함수가 호출됩니다.
: 실행 후 RC가 0이 되면 자동으로 객체를 메모리에서 해제합니다.
class Cat {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) 메모리 해제")
}
}
func doSomething() {
var cuke = Cat(name: "cuke") // cuke 참조카운트 1
}
doSomething()

: 객체에 nil값을 할당하면 RC가 0개가 되기 때문에 자동으로 메모리 해제됩니다.
var cuke: Cat? = Cat(name: "cuke") // cuke 참조카운트 1
var yuni: Person? = Person(name: "Yuni") // yuni 참조카운트 1
cuke = nil // cuke 참조카운트 0 (더 이상 Cat 객체를 참조하지않음.)
yuni = nil // yuni 참조카운트 0 (더 이상 yuni 객체를 참조하지않음.)

: 인스턴스의 주소값이 변수에 할당되면 강한 참조가 발생하며, 이는 참조 횟수를 증가시킵니다. 이 때문에 인스턴스끼리 서로를 가리킬 수 있으며, 이러한 상황이 순환 참조를 발생시킬 수 있습니다.
실제 데이터는 힙에 저장되고, 그 데이터를 참조하는 정보는 스택에 저장됩니다.
class Cat {
var name: String
var owner: Person?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 메모리 해제")
}
}
class Person {
var name: String
var pet: Cat?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 메모리 해제")
}
}
var cuke: Cat? = Cat(name: "cuke") // cuke 참조카운트 1
var yuni: Person? = Person(name: "Yuni") // yuni 참조카운트 1
cuke?.owner = yuni // cuke 참조카운트 2
yuni?.pet = cuke // yuni 참조카운트 2
/// nil을 할당해줘도 해제가 되지 않음.
/// 서로가 서로를 참조하게 됨 (= 강한참조 사이클이 일어남)
cuke = nil // cuke 참조카운트 1
yuni = nil // yuni 참조카운트 1

: 약한 참조는 ARC가 해당 인스턴스에 대한 참조를 해제할 수 있도록 허용하는 참조 방식입니다. 약한 참조로 참조되는 동안에도 해당 인스턴스가 메모리에서 해제될 수 있으며, 이 때 ARC는 해당 참조를 nil로 초기화합니다. 따라서 약한 참조는 언제든지 해제될 수 있으며, 옵셔널 변수에만 사용할 수 있습니다.
class Cat {
var name: String
weak var owner: Person? // 약한 참조
init(name: String) {
self.name = name
}
deinit {
print("\(name) 메모리 해제")
}
}
class Person {
var name: String
var pet: Cat?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 메모리 해제")
}
}
var yuni: Person? = Person(name: "Yuni") // yuni 참조 카운트 1
var cuke: Cat? = Cat(name: "Cuke") // cuke 참조 카운트 1
// 약한 참조로 설정되어 있기 때문에 참조 카운트가 증가하지 않음
cuke?.owner = yuni // cuke 참조 카운트 1
// 인스턴스 해제 시점
cuke = nil // cuke 참조 카운트 0, Cat 객체 해제
yuni = nil // yuni 참조 카운트 0, Person 객체 해제

위 예시는 Cat 클래스의 owner 프로퍼티는 약한 참조로 선언되어 있습니다. 때문에 Person 객체가 메모리에서 해제될 때 Cat 객체가 자동으로 그 참조를 nil로 초기화하게 됩니다. 이러한 방식으로 순환 참조 문제를 방지하고 메모리 누수를 예방할 수 있습니다.
: 미소유 참조는 약한 참조와 유사하지만, 가리키는 인스턴스가 해제되더라도 자동으로 nil을 할당하지 않습니다.
class Cat {
var name: String
unowned var owner: Person // 미소유 참조
init(name: String, owner: Person) {
self.name = name
self.owner = owner
}
deinit {
print("\(name) 메모리 해제")
}
}
class Person {
var name: String
var pet: Cat?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 메모리 해제")
}
}
var yuni: Person? = Person(name: "Yuni") // yuni 참조카운트 1
var cuke: Cat? = Cat(name: "cuke", owner: yuni!) // cuke 참조카운트 1
/// 참조카운트가 0이되어 메모리에서 해제됨
cuke = nil // cuke 참조카운트 0
yuni = nil // yuni 참조카운트 0

: 참조하던 객체가 메모리에서 먼저 해제된 경우에 런타임 에러발생할 수 있습니다.
☑️ 가리키는 인스턴스가 메모리에서 해제되지 않을 것이라는 확신이 있을 때 사용해야 합니다.
var yuni: Person? = Person(name: "Yuni") // yuni 참조카운트 1
var cuke: Cat? = Cat(name: "cuke", owner: yuni!) // cuke 참조카운트 1
yuni = nil // yuni 참조카운트 0
// yuni를 먼저 메모리에서 해제한 후에 cuke의 owner에 접근을 시도
print(cuke?.owner.name) // 런타임 에러 발생
