2025. 1. 19. 19:12ㆍiOS
**Retain Cycle(참조 순환)**은 두 개 이상의 객체가 서로를 강한 참조(Strong Reference)로 참조하는 경우 발생하는 문제로, 메모리 누수를 초래할 수 있습니다. Retain Cycle은 ARC(Automatic Reference Counting) 환경에서 발생할 수 있는 주요 메모리 관리 문제 중 하나입니다. 이 문제를 제대로 처리하지 않으면, 참조된 객체들이 메모리에서 해제되지 않고 계속 남아 있게 되어, 시스템의 메모리 사용이 증가하고 성능 문제가 발생할 수 있습니다.
1. Retain Cycle의 원리
ARC는 각 객체에 대한 **참조 카운트(Reference Count)**를 추적하여 객체의 메모리 관리를 자동으로 처리합니다. 그러나 두 객체가 서로를 강한 참조로 참조하는 경우, 각 객체의 참조 카운트는 0이 될 수 없습니다. 이로 인해 객체들은 서로를 참조한 상태로 계속 메모리에서 존재하게 되며, 결과적으로 메모리 해제가 이루어지지 않게 됩니다. 이것이 바로 Retain Cycle입니다.
예를 들어, 두 객체가 서로를 참조하는 상황을 생각해 봅시다:
예제: Retain Cycle 발생
class Person {
var name: String
var friend: Person?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is deinitialized")
}
}
var personA: Person? = Person(name: "Alice")
var personB: Person? = Person(name: "Bob")
personA?.friend = personB
personB?.friend = personA
personA = nil personB = nil
위 코드에서는 personA와 personB가 서로를 강한 참조로 참조하고 있습니다. 이로 인해 personA와 personB의 참조 카운트는 1로 증가하고, 서로를 참조하면서 참조 카운트가 0으로 떨어지지 않습니다. 따라서 personA와 personB는 메모리에서 해제되지 않습니다. 이로 인해 메모리 누수가 발생하게 됩니다.
2. Retain Cycle의 문제점
Retain Cycle의 주요 문제점은 객체가 메모리에서 해제되지 않아서 메모리 누수가 발생하는 것입니다. 이 문제를 해결하지 않으면, 애플리케이션이 계속해서 메모리를 차지하게 되어 성능 저하나 크래시를 유발할 수 있습니다.
예시 문제
- 앱 성능 저하: 객체들이 메모리에서 해제되지 않으면, 앱이 사용하는 메모리가 계속해서 증가할 수 있습니다. 특히 앱이 장시간 실행되거나 많은 객체가 생성되는 경우, 메모리 부족 현상이나 앱의 크래시를 초래할 수 있습니다.
- 메모리 누수: 불필요한 객체들이 계속 메모리에 남게 되면 메모리 누수가 발생하고, 이로 인해 시스템의 전체적인 성능이 저하됩니다.
3. Retain Cycle을 피하는 방법
Retain Cycle을 방지하는 주요 방법은 객체 간의 참조 관계에서 **강한 참조(Strong Reference)**를 사용하지 않고, **약한 참조(Weak Reference)**나 **미소유 참조(Unowned Reference)**를 사용하여 참조 카운트를 적절히 관리하는 것입니다. 다음은 Retain Cycle을 방지하는 방법들입니다.
1) 약한 참조(Weak Reference) 사용
약한 참조는 참조 카운트를 증가시키지 않으며, 객체가 메모리에서 해제되면 해당 참조는 nil로 자동으로 설정됩니다. 이 방법은 순환 참조가 발생할 수 있는 경우에 유용합니다.
class Person {
var name: String
var friend: Person?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is deinitialized")
}
}
var personA: Person? = Person(name: "Alice")
var personB: Person? = Person(name: "Bob")
personA?.friend = personB
personB?.friend = personA // Retain cycle 방지
personA = nil
personB = nil
위 코드에서 friend 프로퍼티를 weak로 선언하여 Retain Cycle을 방지할 수 있습니다. 이때, personA와 personB는 nil이 되어 메모리에서 해제됩니다.
2) 미소유 참조(Unowned Reference) 사용
미소유 참조는 객체가 메모리에서 해제되면 자동으로 nil로 설정되지 않고, 참조를 유지한 채로 사용할 수 있습니다. 이는 객체가 반드시 메모리에서 해제되지 않고 계속 사용되는 경우에 유용합니다. unowned는 주로 부모-자식 관계와 같은 생명 주기가 밀접하게 연결된 객체에서 사용됩니다.
class Customer {
var name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is deinitialized")
}
}
class CreditCard {
var number: Int
unowned let customer: Customer
init(number: Int, customer: Customer) {
self.number = number
self.customer = customer
}
deinit {
print("CreditCard #\(number) is deinitialized")
}
}
var customer: Customer? = Customer(name: "Alice")
customer?.card = CreditCard(number: 1234, customer: customer!)
customer = nil // CreditCard는 미소유 참조로 해제됨
위 예제에서 CreditCard 클래스의 customer 프로퍼티는 unowned로 선언되어 있습니다. 이 경우, customer가 메모리에서 해제되면 CreditCard 객체는 자동으로 해제됩니다.
3) 캡처 목록(Capture List) 사용
클로저(Closure)에서 순환 참조가 발생하는 경우, 캡처 목록을 사용하여 약한 참조나 미소유 참조로 객체를 캡처할 수 있습니다. 이를 통해 클로저 내에서 객체를 안전하게 참조할 수 있습니다.
class Example {
var name = "Example"
lazy var closure: () -> Void = { [weak self] in
print(self?.name ?? "No name")
}
deinit {
print("\(name) is deinitialized")
}
}
var example: Example? = Example()
example?.closure() // "Example"
example = nil // 메모리 해제
이 코드는 클로저가 Example 객체를 약한 참조(weak self)로 캡처하도록 하여, Example 객체가 메모리에서 해제될 수 있도록 합니다.
4. Retain Cycle을 탐지하는 도구
애플은 Instruments라는 도구를 제공하여 앱에서 메모리 누수나 Retain Cycle을 찾을 수 있습니다. Instruments의 Leaks나 Allocations 도구를 사용하여 객체가 예상보다 오래 메모리에 남아 있는지 추적할 수 있습니다.
결론
Retain Cycle은 객체가 서로를 강한 참조로 참조하여 발생하는 메모리 관리 문제입니다. Retain Cycle은 메모리 누수로 이어져 성능 저하를 초래할 수 있기 때문에, 객체 간의 참조 관계에서 약한 참조(weak) 또는 **미소유 참조(unowned)**를 적절히 사용하여 문제를 해결해야 합니다. 또한, 클로저에서는 **캡처 목록(Capture List)**을 사용하여 순환 참조를 방지하고, Instruments 도구를 활용해 메모리 누수를 탐지하는 것이 중요합니다.
'iOS' 카테고리의 다른 글
[iOS] Thread Safety (0) | 2025.01.19 |
---|---|
[iOS] Concurrency(동시성) (1) | 2025.01.19 |
[iOS] ARC (Automatic Reference Counting) (0) | 2025.01.19 |
[iOS] 카메라(Camera)/앨범(Album) 권한 (0) | 2025.01.13 |
[RxSwift] PublishSubject (0) | 2025.01.10 |