Swift) 강한 참조와 순환 참조, weak self와 unowned self의 차이

1. 참조 타입과 메모리 관리의 기초

  • Swift에서의 메모리 관리
    Swift는 ARC(Automatic Reference Counting)를 사용해 메모리를 관리합니다. 이는 객체의 참조 수를 추적하여 더 이상 사용되지 않는 객체를 자동으로 해제합니다.
  • 강한 참조(Strong Reference)
    객체가 다른 객체를 참조할 때 기본적으로 강한 참조를 사용합니다. 강한 참조는 참조 카운트를 증가시키며, 이로 인해 객체가 메모리에서 해제되지 않게 합니다.

2. 순환 참조와 메모리 누수

  • 순환 참조의 정의
    두 객체가 서로를 강하게 참조하면, 두 객체는 서로의 참조 카운트를 증가시키며, 더 이상 사용되지 않더라도 ARC가 객체를 해제하지 못하는 상태가 됩니다. 이를 **순환 참조(Retain Cycle)**라고 합니다.
  • 순환 참조의 예제
class Person {
    var pet: Pet?
    deinit {
        print("Person deinitialized")
    }
}

class Pet {
    var owner: Person?
    deinit {
        print("Pet deinitialized")
    }
}

var person: Person? = Person()
var pet: Pet? = Pet()

person?.pet = pet
pet?.owner = person

person = nil // Person 객체가 메모리에서 해제되지 않음
pet = nil    // Pet 객체도 메모리에서 해제되지 않음

3. weak self와 unowned self

순환 참조를 방지하기 위해 Swift에서는 weak와 unowned 키워드를 제공합니다. 두 키워드는 강한 참조 대신 약한 참조를 사용하도록 설정합니다.

3.1 weak self

  • 특징
    • 참조 대상이 메모리에서 해제되면 자동으로 nil이 됩니다.
    • 참조는 옵셔널(Optional)이므로 사용 시 반드시 언래핑해야 합니다.
  • 적용 예시
class ViewController {
    var name = "Declan"
    func fetchData() {
        DispatchQueue.global().async { [weak self] in
            guard let self = self else { return }
            print(self.name)
        }
    }
}

3.2 unowned self

  • 특징
    • 참조 대상이 메모리에서 해제되었는데도 접근하면 런타임 에러가 발생합니다.
    • 옵셔널이 아니므로 강제 언래핑 없이 사용 가능합니다.
    • 참조 대상이 해제되지 않는다는 전제가 있을 때 사용해야 안전합니다.
  • 적용 예시
class ViewController {
    var name = "Declan"
    lazy var printName: () -> Void = { [unowned self] in
        print(self.name)
    }
}

4. weak self와 unowned self의 선택 기준

  • weak self 사용:
    • 대상 객체의 수명이 불확실하거나, 메모리에서 해제될 가능성이 있을 때 사용합니다.
    • 예: 네트워크 요청, 비동기 작업 등.
  • unowned self 사용:
    • 대상 객체가 반드시 수명이 동일하거나 더 긴 경우에 사용합니다.
    • 예: 부모-자식 관계에서 자식이 부모를 참조할 때.

5. 강한 참조 방지 패턴

  • [weak self] 패턴
    강한 참조를 피하기 위해 자주 사용하는 패턴입니다.
closure { [weak self] in
    guard let self = self else { return }
    // self를 안전하게 사용
}
  • [unowned self] 패턴
    자주 사용되지는 않지만, 강력한 성능 요구 사항이나 수명이 보장될 때 유용합니다.

6. 순환 참조 해결을 위한 실전 팁

  • 클로저 내부에서 self 참조를 강하게 가지지 않도록 [weak self]나 [unowned self]를 적극적으로 사용하세요.
  • 클래스 간 강한 참조 대신 delegate를 사용할 때도 약한 참조를 적용하세요.
protocol Delegate: AnyObject {
    func didSomething()
}

class A {
    weak var delegate: Delegate?
}
  • 뷰 계층에서는 @escaping 클로저를 다룰 때 특히 주의해야 합니다.

Leave a Comment