**강한 참조(Strong Reference)**와 **약한 참조(Weak Reference)**는 Swift에서 메모리 관리와 객체 생명주기를 관리하기 위해 사용하는 참조 방식입니다. 이 두 참조 방식은 ARC(Automatic Reference Counting) 시스템을 통해 메모리를 효율적으로 관리하는 데 중요한 역할을 합니다. 객체가 강한 참조로 연결되어 있으면 ARC가 객체를 메모리에서 해제하지 않고 유지하며, 약한 참조는 객체가 메모리에서 해제되도록 허용하여 순환 참조(Strong Reference Cycle) 문제를 방지할 수 있습니다.
강한 참조(Strong Reference)
- 정의: 강한 참조는 기본 참조 방식으로, 객체의 참조 횟수를 증가시키며 참조하는 동안 객체가 메모리에서 해제되지 않도록 합니다.
- 특징: 강한 참조를 사용하면, 참조가 있는 동안 객체의 생명주기가 유지되며, 해당 객체는 ARC에 의해 메모리에서 해제되지 않습니다.
- 사용 예시: 대부분의 속성은 기본적으로 강한 참조를 사용하며, 일반적인 객체 간의 참조에서 사용됩니다.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var person1 = Person(name: "Alice") // person1이 Person 인스턴스를 강한 참조함
var person2 = person1 // person2도 Person 인스턴스를 강한 참조함
person1 = nil // person2가 여전히 강한 참조 중이므로 Person 인스턴스는 해제되지 않음
위 예제에서 person1과 person2는 모두 Person 인스턴스를 강한 참조로 가리킵니다. 하나의 참조가 해제되어도 다른 참조가 남아있다면, 객체는 메모리에서 해제되지 않습니다.
약한 참조(Weak Reference)
- 정의: 약한 참조는 객체의 참조 횟수를 증가시키지 않는 참조 방식입니다. 객체가 다른 강한 참조가 없으면 ARC가 메모리에서 해제할 수 있도록 허용합니다.
- 특징: 약한 참조는 참조 대상이 해제되면 nil이 되며, 순환 참조 문제를 방지하는 데 유용합니다. 일반적으로 뷰 컨트롤러의 델리게이트나 순환 참조가 발생할 수 있는 상황에서 사용합니다.
- 사용 방법: 약한 참조는 선언 시 weak 키워드를 사용하며, 옵셔널 타입이어야 합니다(var로 선언).
class Person {
var name: String
init(name: String) {
self.name = name
}
}
class Apartment {
var unit: String
weak var tenant: Person? // 약한 참조로 선언하여 순환 참조 방지
init(unit: String) {
self.unit = unit
}
}
var person = Person(name: "Alice")
var apartment = Apartment(unit: "101")
apartment.tenant = person // 약한 참조로 tenant가 person을 참조
person = nil // Person 인스턴스 해제
print(apartment.tenant) // 출력: nil (person이 해제되면서 tenant도 nil)
위 예제에서 Apartment 클래스의 tenant 속성은 약한 참조로 Person 객체를 참조합니다. person 변수가 nil이 되면 tenant 참조도 자동으로 nil이 되어 메모리 누수를 방지할 수 있습니다.
요약
- 강한 참조는 기본 참조 방식으로, 객체 생명주기를 유지합니다. 객체가 강한 참조로 연결되어 있는 한, 메모리에서 해제되지 않습니다.
- 약한 참조는 참조 횟수를 증가시키지 않고 객체가 메모리에서 해제될 수 있도록 허용합니다. 이를 통해 순환 참조를 방지하고 메모리 누수를 줄일 수 있습니다.
순환 참조(Retain Cycle)가 발생하는 경우와 해결 방법은 무엇인가요?
**순환 참조(Retain Cycle)**는 두 객체가 서로를 강하게 참조할 때 발생하는 문제로, 서로가 참조를 유지하고 있어서 메모리에서 해제되지 않는 상태를 의미합니다. Swift에서는 ARC(Automatic Reference Counting)가 메모리를 관리하지만, 순환 참조가 발생하면 ARC가 객체의 참조 횟수를 0으로 만들지 못해 메모리 누수가 발생합니다. 순환 참조는 주로 클로저와 델리게이트 패턴에서 발생할 수 있으며, 약한 참조와 미소유 참조를 사용해 해결할 수 있습니다.
1. 클래스 간의 상호 참조
두 클래스가 서로를 강하게 참조하는 경우, 양쪽 모두 참조가 남아있어서 해제되지 않는 상태가 됩니다.
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
}
}
var person = Person(name: "Alice")
var apartment = Apartment(unit: "101")
person.apartment = apartment
apartment.tenant = person
// person과 apartment는 서로 강하게 참조하여 순환 참조 발생
위 코드에서 Person 인스턴스는 Apartment 인스턴스를 강하게 참조하고, Apartment 인스턴스 역시 Person 인스턴스를 강하게 참조하여 순환 참조가 발생합니다.
2. 클로저에서의 강한 참조
클로저는 자신이 캡처하는 객체를 기본적으로 강하게 참조하므로, 클로저 내부에서 객체의 프로퍼티나 메서드를 참조할 때 순환 참조가 발생할 수 있습니다.
class ViewController {
var name = "Main ViewController"
lazy var printName: () -> Void = {
print(self.name) // self를 강하게 참조하여 순환 참조 발생
}
}
let viewController = ViewController()
viewController.printName()
순환 참조의 해결 방법
1. 약한 참조(weak) 사용
서로를 참조하는 두 객체 중 하나를 **약한 참조(weak)**로 선언하여, 참조 횟수를 증가시키지 않고 객체가 해제될 수 있도록 합니다. 주로 델리게이트 패턴에서 많이 사용됩니다.
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
}
class Apartment {
let unit: String
weak var tenant: Person? // 약한 참조로 선언하여 순환 참조 방지
init(unit: String) {
self.unit = unit
}
}
var person = Person(name: "Alice")
var apartment = Apartment(unit: "101")
person.apartment = apartment
apartment.tenant = person
// 약한 참조로 인해 순환 참조가 발생하지 않음
위 코드에서 Apartment의 tenant 속성을 weak으로 선언하여 순환 참조를 방지합니다. 이제 person이 메모리에서 해제되면, apartment의 tenant도 자동으로 nil이 됩니다.
2. 미소유 참조(unowned) 사용
미소유 참조(unowned)는 약한 참조와 비슷하지만, nil을 허용하지 않는 경우 사용합니다. 미소유 참조는 객체가 항상 존재할 것으로 확신할 때 사용하며, 객체가 해제될 때까지 미소유 참조가 참조를 유지합니다.
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
}
class CreditCard {
let number: Int
unowned let customer: Customer // 미소유 참조로 선언하여 순환 참조 방지
init(number: Int, customer: Customer) {
self.number = number
self.customer = customer
}
}
let customer = Customer(name: "Alice")
customer.card = CreditCard(number: 1234_5678_9012_3456, customer: customer)
위 코드에서 CreditCard의 customer 속성은 unowned로 선언되었으므로, customer가 해제될 때 card는 자동으로 nil로 설정되며, 순환 참조가 발생하지 않습니다.
3. 클로저에서 캡처 목록(Capture List) 사용
클로저가 객체를 강하게 참조하지 않도록 캡처 목록을 사용하여 참조 유형을 약한 참조 또는 미소유 참조로 지정할 수 있습니다.
class ViewController {
var name = "Main ViewController"
lazy var printName: () -> Void = { [weak self] in // 캡처 목록으로 약한 참조 사용
guard let self = self else { return }
print(self.name)
}
}
let viewController = ViewController()
viewController.printName()
위 코드에서 [weak self]를 사용해 self를 약한 참조로 캡처하여 클로저와 self 간의 순환 참조를 방지합니다. 만약 self가 해제된 경우 nil로 설정되어 클로저 내부에서 안전하게 nil 체크를 할 수 있습니다.
요약
- 순환 참조는 두 객체가 서로 강하게 참조하여 ARC가 해제하지 못하는 상태를 의미합니다.
- 해결 방법:
- 약한 참조(weak): nil이 될 수 있는 경우, 참조 횟수를 증가시키지 않도록 약한 참조 사용.
- 미소유 참조(unowned): nil이 될 수 없는 경우 사용하며, 객체가 항상 존재한다고 확신할 때 사용.
- 클로저에서 캡처 목록(Capture List): 클로저가 self를 캡처할 때 [weak self]나 [unowned self]를 사용하여 순환 참조를 방지.
순환 참조 문제를 예방하려면 객체 간의 참조 관계를 주의 깊게 설계하고, weak, unowned, 그리고 클로저 캡처 목록을 적절히 사용하여 메모리 누수를 방지하는 것이 중요합니다.
클로저에서 [weak self]와 [unowned self]의 차이는 무엇인가요?
클로저에서 **[weak self]**와 **[unowned self]**는 **순환 참조(Retain Cycle)**를 방지하기 위해 self를 약하게 캡처하는 방법이지만, 객체의 생명주기와 self의 해제 가능성에 따라 선택하는 방식에 차이가 있습니다.
1. [weak self]
- 정의: [weak self]는 약한 참조를 사용하여 클로저가 self를 참조하도록 만듭니다.
- 특징: 약한 참조는 객체가 메모리에서 해제될 수 있도록 허용하므로, 참조된 객체가 해제되면 nil이 됩니다. 따라서, [weak self]를 사용할 때 self는 옵셔널(self?)로 처리해야 합니다.
- 사용 상황: [weak self]는 객체가 해제될 수 있는 가능성이 있는 경우에 사용합니다. 보통 클로저 내부에서 self의 존재 여부를 체크하고 안전하게 사용하기 위해 많이 사용됩니다.
class ViewController {
var name = "Main ViewController"
func setupClosure() {
let closure = { [weak self] in
guard let self = self else {
print("self가 해제되었습니다.")
return
}
print("ViewController name: \(self.name)")
}
closure()
}
}
위 코드에서 [weak self]를 사용하여 클로저가 self를 약하게 참조하므로, self가 해제되면 클로저 내에서 self가 nil로 설정됩니다. 따라서 self가 해제된 경우, guard let self = self else { ... } 구문을 통해 안전하게 self의 존재를 체크하고 처리할 수 있습니다.
2. [unowned self]
- 정의: [unowned self]는 미소유 참조를 사용하여 클로저가 self를 참조하게 합니다.
- 특징: [unowned self]는 self가 항상 존재할 것으로 가정하므로 옵셔널이 아니며, self가 해제되어도 nil이 되지 않습니다. 하지만 self가 해제된 후 참조하면 런타임 에러가 발생할 수 있습니다.
- 사용 상황: [unowned self]는 클로저가 self보다 먼저 해제되지 않고, self가 항상 존재한다고 확신할 때 사용합니다. 예를 들어, 두 객체가 함께 해제되거나, 서로를 참조하지 않아 객체가 해제될 가능성이 없는 경우에 사용합니다.
class ViewController {
var name = "Main ViewController"
func setupClosure() {
let closure = { [unowned self] in
print("ViewController name: \(self.name)")
}
closure()
}
}
위 코드에서 [unowned self]를 사용하여 self를 미소유 참조로 캡처합니다. 클로저가 self를 참조할 때 self가 반드시 존재할 것이라고 가정하므로 옵셔널이 아니며, self가 해제되었을 경우 런타임 에러가 발생할 수 있습니다.
언제 [weak self]와 [unowned self]를 사용할지 선택하는 기준
- [weak self]: self가 해제될 수 있으며, 클로저가 self보다 오래 유지되거나, self의 해제 여부를 안전하게 확인하고 싶을 때 사용합니다. 예를 들어, 뷰 컨트롤러에서 클로저가 비동기 작업을 수행할 때 self가 해제될 가능성이 있다면 [weak self]를 사용하여 안전하게 처리합니다.
- [unowned self]: self가 항상 존재할 것으로 확신할 때 사용하며, 두 객체가 동시에 해제되는 상황에서 사용합니다. 예를 들어, 상호 강한 참조가 필요 없는 두 객체가 함께 생명주기를 공유할 때 사용하면 안전합니다.
class ViewController {
var name = "Main ViewController"
func performTask() {
// Weak reference: 클로저가 완료되기 전에 self가 해제될 수 있음
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
print("Weak reference: \(self.name)")
}
// Unowned reference: 클로저가 완료되기 전까지 self가 반드시 해제되지 않아야 함
let closure = { [unowned self] in
print("Unowned reference: \(self.name)")
}
closure() // 사용 즉시 실행
}
}
요약
- Weak Reference: 비동기 작업을 수행하는 경우, self가 해제될 가능성이 있으므로 안전하게 옵셔널로 처리하여 nil 체크를 진행합니다.
- Unowned Reference: 클로저가 즉시 실행되므로 self가 존재한다고 확신할 수 있어 미소유 참조로 사용해 안전하게 접근합니다.
결론적으로, [weak self]는 self가 해제될 수 있는 상황에서 안전하게 사용할 수 있고, [unowned self]는 self가 반드시 존재할 것으로 확신하는 상황에서 사용하여 메모리 누수 없이 코드 안정성을 유지할 수 있습니다.
'정보 > 레벨 1' 카테고리의 다른 글
UIKit에서 TableView와 CollectionView의 차이점은 무엇인가요? (0) | 2024.11.14 |
---|---|
iOS 앱에서 Multi-threading을 구현하는 방법은 무엇인가요? (0) | 2024.11.13 |
Swift의 에러 처리 방법에 대해 설명해주세요. (0) | 2024.11.13 |
Git에서 브랜치(Branch)를 사용하는 이유와 장점은 무엇인가요? (0) | 2024.11.12 |
Swift의 고차 함수(Higher-Order Functions)에 대해 설명해주세요. (0) | 2024.11.12 |