ARC (Automatic Reference Counting)
ARC는 Swift에서 참조 타입(클래스 인스턴스)의 메모리 관리를 자동으로 처리하는 메커니즘.
런타임 시 객체의 참조 횟수를 추적하여 객체가 더 이상 사용되지 않을 때 메모리를 해제하는 방식으로 메모리 누수 방지.
Swift에서는 개발자가 직접 메모리를 관리하지 않고, ARC가 자동으로 메모리 해제를 처리해주기 때문에 메모리 관리가 간편.
그러나 참조 사이클과 같은 메모리 누수를 유발할 수 있는 문제를 이해하고 적절히 해결해야 함.
1. ARC 작동 방식
ARC는 객체가 생성될 때 그 객체를 참조하는 참조 카운트(reference count)를 증가시키고, 더 이상 참조되지 않으면 감소시킴.
참조 카운트가 0이 되면 ARC는 해당 객체를 메모리에서 해제.
1.1. 기본적인 ARC 동작 예시
class Person {
let name: String
init(name: String) {
self.name = name
print("\\\\(name) is being initialized")
}
deinit {
print("\\\\(name) is being deinitialized")
}
}
var person1: Person? = Person(name: "Alice")
var person2: Person? = person1 // 참조 카운트 증가
person1 = nil // 참조 카운트 감소, 하지만 person2가 참조 중
person2 = nil // 참조 카운트 0, 메모리에서 해제
- person1이 Person 인스턴스를 참조하면서 참조 카운트 1 증가.
- person2가 같은 인스턴스를 참조하여 참조 카운트가 2로 증가.
- person1이 nil이 되면서 참조 카운트가 1로 감소하고, person2가 nil이 되면서 참조 카운트가 0이 되어 메모리에서 해제.
1.2. deinit 메소드
- deinit은 객체가 메모리에서 해제될 때 호출되는 소멸자.
- 메모리 해제 시점에 특정 작업을 수행해야 할 때 이 메소드를 사용.
2. 참조 종류
ARC에서는 강한 참조, 약한 참조, 그리고 미소유 참조(unowned reference)라는 세 가지 참조 방식이 존재함.
이 참조 방식은 참조 사이클을 방지하는 데 중요한 역할.
2.1. 강한 참조 (Strong Reference)
- 기본 참조 방식이며, 참조하는 동안 객체의 참조 카운트를 증가시킴.
- 강한 참조는 객체가 해제되지 않도록 보호하므로, 참조 카운트를 0으로 만드는 데 영향을 미침.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var person1 = Person(name: "John") // 참조 카운트 1
var person2 = person1 // 참조 카운트 2
2.2. 약한 참조 (Weak Reference)
- 약한 참조(weak)는 참조 카운트를 증가시키지 않고, 객체가 해제되면 자동으로 nil로 설정.
- 순환 참조(circular reference) 문제를 해결하기 위해 주로 사용.
class Person {
let name: String
var friend: Person?
init(name: String) {
self.name = name
}
}
var person1: Person? = Person(name: "John")
var person2: Person? = Person(name: "Alice")
person1?.friend = person2
person2?.friend = person1 // 순환 참조 발생
- person1과 person2가 서로를 참조하는 순환 참조 문제 발생.
- 이를 해결하기 위해 약한 참조를 사용.
class Person {
let name: String
weak var friend: Person? // 약한 참조 사용
init(name: String) {
self.name = name
}
}
var person1: Person? = Person(name: "John")
var person2: Person? = Person(name: "Alice")
person1?.friend = person2
person2?.friend = person1 // 순환 참조 해결
- 약한 참조는 참조 카운트를 증가시키지 않으며, 참조하던 객체가 해제되면 자동으로 nil로 설정.
2.3. 미소유 참조 (Unowned Reference)
- 미소유 참조(unowned)는 참조 카운트를 증가시키지 않지만, 객체가 해제된 후에도 nil로 설정되지 않으며, 해제된 후에 접근하면 런타임 오류 발생.
- 미소유 참조는 참조 대상이 항상 해제되지 않음을 보장할 때 사용.
- 예를 들어, 상위 객체가 하위 객체를 참조할 때 하위 객체가 상위 객체보다 오래 생존하지 않을 경우 사용할 수 있음.
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("\\\\(name) is being deinitialized")
}
}
class CreditCard {
let number: Int
unowned let customer: Customer // 미소유 참조
init(number: Int, customer: Customer) {
self.number = number
self.customer = customer
}
deinit {
print("Card \\\\(number) is being deinitialized")
}
}
var john: Customer? = Customer(name: "John")
john?.card = CreditCard(number: 1234, customer: john!)
john = nil // Customer와 CreditCard 모두 해제됨
- CreditCard는 고객(Customer)의 정보를 미소유 참조(unowned).
- 고객이 해제되면 카드도 함께 해제되므로 참조 사이클이 발생하지 않음.
3. ARC와 순환 참조 (Retain Cycle)
3.1. 순환 참조란?
순환 참조는 두 객체가 서로를 강하게 참조하여, 참조 카운트가 0이 되지 않는 상황.
이때 ARC는 두 객체가 참조되고 있다고 판단하여 메모리를 해제하지 않으며, 이는 메모리 누수를 초래할 수 있음
3.2. 순환 참조 해결 방법
순환 참조 문제를 해결하기 위해선 약한 참조(weak) 또는 미소유 참조(unowned)를 사용.
이러한 참조는 참조 카운트를 증가시키지 않으므로, 메모리 누수를 방지할 수 있음.
4. ARC의 동작 요약
- 객체가 생성될 때, 참조 카운트가 1로 설정.
- 새로운 변수가 객체를 참조하면 참조 카운트가 증가.
- 변수가 더 이상 객체를 참조하지 않으면 참조 카운트가 감소.
- 참조 카운트가 0이 되면 ARC가 자동으로 메모리를 해제.
- 순환 참조를 방지하기 위해 약한 참조(weak)나 미소유 참조(unowned)를 사용하는 것이 좋음.
5. ARC와 성능
- 자동 메모리 관리
- ARC는 개발자가 직접 메모리를 해제할 필요 없이, 참조 카운트를 기반으로 객체가 더는 사용되지 않으면 자동으로 메모리 해제.
- 이는 코드의 안정성과 성능을 높임.
- 낮은 오버헤드
- ARC는 런타임 성능에 미치는 오버헤드가 적으며, 메모리 관리를 효율적으로 처리.
- 단, 순환 참조 문제가 발생하면 메모리 누수가 발생할 수 있으므로 주의해야 함.
6. 정리
ARC는 참조 타입의 메모리를 자동으로 관리해주는 메커니즘으로, 개발자가 직접 메모리 관리를 할 필요 없이 객체의 생명 주기를 관리.
ARC는 참조 카운트를 기반으로 메모리 해제를 처리하며, 순환 참조 문제를 방지하기 위해 약한 참조(weak)와 미소유 참조(unowned)를 사용해야 함.
- 강한 참조는 객체의 참조 카운트를 증가시켜 메모리를 유지.
- 약한 참조는 참조 카운트를 증가시키지 않으며, 참조된 객체가 해제되면 자동으로 nil로 설정.
- 미소유 참조는 참조 카운트를 증가시키지 않지만, 참조된 객체가 해제된 후에도 nil로 설정되지 않음.
'Swift' 카테고리의 다른 글
[Swift] Swift Concurrency (0) | 2024.04.15 |
---|---|
[Swift] GCD (0) | 2024.04.13 |
[Swift] mutating (0) | 2024.04.12 |
[Swift] inout (0) | 2024.04.08 |
[Swift] Escaping Closure (@escaping) (0) | 2024.04.05 |