왜 delegate는 weak로?

고라니·2024년 1월 25일
1

TIL

목록 보기
57/67

Delegate 패턴을 적용할 때 delegate를 weak로 정의하는 것을 권장한다. 그래서 습관처럼 weak로 선언해왔다.
ARC에 대해 알게 되면서 왜 delegate를 weak로 선언하는 것이 권장되는지 한번 고민해보고자 한다.

weak로 정의해야 하는 이유?

Delegate를 weak로 선언하는 것을 권장하는 이유는 순환 참조(retain cycle)를 방지하기 위함이다. 두 객체가 서로를 강한(strong)참조할 때 순환 참조가 발생하고 이는 메모리 누수로 이어질 수 있다. Delegate 패턴에서는 이러한 순환 참조를 방지하기 위해 delegate를 weak로 선언하여 안전하게 메모리 관리를 할 수 있다.

차근차근 살펴보기

class SomeManager {
    var delegate: SomeManagerDelegate?

    func somthing() {
        delegate?.didSomething()
    }
}

protocol SomeManagerDelegate {
    func didSomething()
}

class SomeViewcontroller: UIViewController, SomeManagerDelegate {
    let someManager = SomeManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        someManager.delegate = self
    }

    func didSomething() {
        // 델리게이트 메서드 구현
    }
}

일반적인 Delegate패턴은 위와 같다.
기본 홈 화면인 HomeViewController에서 네비게이션을 통해 ViewController의 화면을 띄웠다고 가정해보자.

1. 초기화, 참조카운트 증가


네비게이션에 의해 ViewController 초기화, ViewController 참조 카운트 +1

ViewController에 의해 SomeManager인스턴스 생성되고, 참조 카운트 +1

ViewContller의 ViewDidLoad 내부에서 SomeManager인스턴스의 delegate를 self로 참조, ViewController 참조 카운트 +1

window가 ViewController인스턴스 참조
ViewController가 SomeManager인스턴스 참조
SomeManager가 ViewController인스턴스 참조
ViewController의 참조카운트: 2, SomeManager의 참조카운트: 1

2. 메모리 해제

사용자 ViewController 화면에서의 작업을 모두 마치고 처음 홈화면으로 이동하고 ViewController의 인스턴스는 메모리에서 해제 되어야 한다.

네비게이션 스택에서 pop, ViewController인스턴스 참조 카운트 -1

하지만 SomeManager의 delegate가 ViewController인스턴스를 참조하고 있기 때문에 참조 카운트는 0이 되지 않고 1만 감소한 1이 된다.

3. 순환 참조 발생

SomeManager인스턴스와 ViewContrller인스턴스가 서로를 참조하고 있기 때문에 참조 카운트는 0이 될 수 없고 결국 순환 참조가 발생한다.
그.래.서 delegate는 weak로 선언하는것을 권장하는 것이다!

4. 해결하기

이미 알고 있듯이 delegate를 weak로 선언하면 순환 참조 문제를 해결할 수 있다.
delegate를 self로 할당해도 delegate가 weak로 선언되었다면 참조 카운트가 증가하지 않아서 네비게이션스택에서 pop되었을 때 참조 카운트가 0이 되고 ViewController가 메모리에서 해제 되고 ViewController에서 참조하고 있던 someManager도 메모리에서 해제된다.

class SomeManager {
    weak var delegate: SomeManagerDelegate?

    func somthing() {
        delegate?.didSomething()
    }
}

무조건 weak를 사용해야 할까?

위에서 설명한대로, 핵심은 순환 잠조의 가능성이다. 앱 생명주기와 동일한 델리게이트 객체가 존재하고 해당 객체의 delegate가 직접 다른 객체들로 변경되거나 nil이 할당될 수 있는 경우, 순환 참조가 발생하지 않아 strong으로 선언되어도 문제가 발생하지 않을 수 있다.

하지만 이런 상황은 예외적이다. 이러한 예외적인 상황의 경우에서도 순환 참조의 가능성은 주의해야 한다. 결국 delegate를 선언할 때는 안전정을 위해 웬만해서는 weak로 선언하는 것이 권장된다.

마치면서

ARC에 대해 알아본 후 습관처럼 weak로 선언하는 delegate에대해 알아보았다. weak로 선언하는 이유는 델리게이트 패턴에서 delegate를 strong으로 선언하면 순환 참조가 발생할 확률이 아주 높다. 그렇기 때문에 weak로 선언하는것을 권장하는 것이다. 이러한 규칙은 코드를 더욱 안전하게 유지하는데 기여할 수 있다.

profile
🍎 무럭무럭

0개의 댓글