ARC는 Apple이 Xcode에서 제공하는 자동 메모리 관리 기능으로, 앱의 메모리 관리를 개발자가 직접 하지 않아도 되도록 도와준다.
ARC는 객체의 참조 횟수(reference count) 를 추적한다.
ARC의 주요 장점 중 하나는
retain cycle(순환 참조) 이나 dangling pointer(해제된 객체를 가리키는 포인터) 와 같은 일반적인 메모리 관리 문제를 예방할 수 있다는 점이다.
필요한 경우 개발자가 weak, unowned 참조를 사용하도록 유도함으로써,
순환 참조를 인식하고 끊을 수 있게 도와준다.
❗️ ARC가 대부분의 메모리 관리를 자동으로 처리해 주기 때문에,
strong / weak / unowned 참조가 어떻게 동작하는지 정확히 이해하는 것이 중요
Swift에서 메모리 관리는 ARC를 중심으로 이루어진다.
컴파일러는 ARC를 통해 앱에서 사용되는 메모리를 자동으로 추적하고, 더 이상 필요하지 않은 객체를 적절한 시점에 해제한다.
하지만 다음과 같은 경우에는 메모리에 대한 더 깊은 이해가 필요하다.
강한 참조 순환(strong reference cycle) 또는 retain cycle은
두 개 이상의 객체가 서로를 강하게 참조하여 참조 횟수가 절대 0이 되지 않는 상황을 말한다.
이 경우 객체는 메모리에서 해제되지 않는다.
class Person {
var name: String
var car: Car?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deallocated")
}
}
class Car {
var brand: String
var owner: Person?
init(brand: String) {
self.brand = brand
}
deinit {
print("\(brand) is being deallocated")
}
}
var john: Person?
var tesla: Car?
john = Person(name: "John")
tesla = Car(brand: "Tesla")
john?.car = tesla
tesla?.owner = john
위 코드에서는 Person의 john 인스턴스와
Car의 tesla 인스턴스가 서로를 강하게 참조하고 있다.
이 상태에서 john = nil, tesla = nil을 해도
서로를 잡고 있기 때문에 참조 카운트는 0이 되지 않고,
두 객체 모두 메모리에서 해제되지 않는다.
iOS에서는 retain cycle을 끊기 위해 weak 또는 unowned 참조를 사용한다.
class Person {
var name: String
weak var car: Car?
}
unowned는 weak와 유사하지만 중요한 차이점이 있다.
class Parent {
var name: String
var child: Child?
init(name: String) {
self.name = name
}
}
class Child {
var name: String
unowned var parent: Parent
init(name: String, parent: Parent) {
self.name = name
self.parent = parent
}
}
위 예제에서는 Child가 Parent 없이 존재할 수 없기 때문에
unowned 참조를 사용하는 것이 안전하다.
❗️ 만약 참조 대상 객체가 해제될 가능성이 있다면
반드시 weak를 사용해야 한다.
{ [weak self] in
self?.doSomething()
}
클로저 내부에서 self를 약하게 참조하여 순환 참조 방지
→ 반드시 안전하게 unwrap 필요
deinit은 객체가 정상적으로 해제될 때 호출