delegate는 항상 weak var로 선언해야 될까?
항상 습관적으로 delegate을 weak var로 선언을 하던 중, 항상 weak를 써줘야 할까라는 의문이 들었습니다. 아래의 예를 통해서 언제 써줘야 하는지 알아보겠습니다!
예시 프로젝트
- 왼쪽부터 firstVC, secondVC, thirdVC입니다.
- firstVC -> secondVC -> thirdVC로 갔다가, 다시 dismiss하면서 처음 firstVC로 가는 코드를 작성해 보겠습니다.
- label의 text를 바꾸는 등, 불필요한 코드가 있지만, 값 참조하는 부분만 확인해 주시면 됩니다!
- 함수, delegate, 프로퍼티 naming은 간략하게 했습니다!
FirstViewController
- button을 누르면 SecondVC로 넘어가게 됩니다.
class FirstViewController: UIViewController {
@IBOutlet weak var label: UILabel!
@IBAction func buttonTapped(_ sender: Any) {
if let scene = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController{
present(scene, animated: true)
}
}
override func viewDidLoad() {}
}
SecondViewController
- ThirdVC로 가는 함수가 존재하고, dismiss 되어 FirstVC로 가는 코드도 존재합니다.
- ThirdVC의 delegate이 자신임을 설정합니다.
class SecondViewController: UIViewController, ThirdViewControllerDelgate {
@IBOutlet weak var secondLabel: UILabel!
func thirdViewControllerDidTapped(_ viewController: UIViewController) {
secondLabel.text = "변경됨!"
}
@IBAction func goToThirdVCButtonTapped(_ sender: Any) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ThirdViewController") as? ThirdViewController{
vc.delegate = self
present(vc, animated: true)
}
}
@IBAction func goToFirstVCButtonTapped(_ sender: Any) {
dismiss(animated: true)
}
override func viewDidLoad() {}
deinit {
print("SecondViewController-deinited")
}
}
ThirdViewController
- delegate 변수를 강한 참조로 정의합니다.(weak 키워드 x)
- button을 통하여 third VC로 dimiss 할 수 있습니다.
- delegate 메소드를 호출합니다. (SecondViewController의 thirdViewControllerDidTapped가 호출된다!)
class ThirdViewController: UIViewController {
@IBAction func thirdButtonTapped(_ sender: Any) {
delegate?.thirdViewControllerDidTapped(self)
dismiss(animated: true)
}
var delegate: ThirdViewControllerDelgate?
override func viewDidLoad() {}
deinit {
print("ThirdViewController-deinited")
}
}
실행 화면은 아래와 같습니다.!
위와 같이 코드를 구현하면 순환 참조가 발생할까요?
ThirdViewController-deinited
SecondViewController-deinited
순환 참조가 발생하지 않습니다!
전체 scene이 로드가 된 상태에서의 SecondVC와 ThirdVC의 retain count는 아래와 같습니다!
이렇게 될 경우, ThridVC가 dismiss가 되면서 ThirdVC = nil이 되므로 ThirdViewController instance를 참조하는 값이 없어지므로 retain count가 0이 됩니다. 따라서 해당 객체 또한 메모리에서 할당 해제가 됩니다!
자연스럽게 SecondViewController instance의 retain count도 1로 바뀌게 되고, SecondVC가 dismiss가 되어 FirstVC로 이동을 하게 되면 retain count가 -1이 되면서 SecondViewController 인스턴스의 참조 값은 0이 됩니다. 최종적으로 SecondViewController 객체 또한 메모리에서 할당 해제가 됩니다!
즉, weak var를 사용하지 않아도 순환 참조가 일어나지 않는다!
그렇다면, 언제 순환 참조가 일어나게 될까?
SecondViewController가 property로 thirdVC를 갖고 있게 코드를 변경해 보겠습니다!
class SecondViewController: UIViewController, ThirdViewControllerDelgate {
@IBOutlet weak var secondLabel: UILabel!
var thirdVC: UIViewController?
func thirdViewControllerDidTapped(_ viewController: UIViewController) {
secondLabel.text = "변경됨!"
}
@IBAction func goToThirdVCButtonTapped(_ sender: Any) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ThirdViewController") as? ThirdViewController{
vc.delegate = self
thirdVC = vc
present(vc, animated: true)
}
}
@IBAction func goToFirstVCButtonTapped(_ sender: Any) {
dismiss(animated: true)
}
override func viewDidLoad() {}
deinit {
print("SecondViewController-deinited")
}
}
이렇게 될 경우, 아래와 같은 retain count를 가지게 됩니다.
위와 같은 상황일 경우, ThirdVC가 dismiss가 될 때, ThirdViewController의 retain count가 하나 빠지게 되지만, SecondViewController 객체가 참조를 하고 있으므로, 메모리에서 할당 해제되지 않습니다.(ThirdVC - retain count 1)
그리고 SecondVC가 dismiss가 될 경우에도, SecondViewController의 retain count가 하나 빠지게 되지만, ThirdViewController 객체가 참조를 하고 있으므로(delegate) 메모리에서 할당 해제되지 않습니다.(retain count 1)
즉, 순환 참조가 발생합니다!
그렇다면 weak var를 활용하면 어떻게 될까요?
weak var delegate: ThirdViewControllerDelgate?
여기서 잠깐!✋ weak var를 사용하면 프로토콜에 AnyObject or Class를 채택해야 되는 이유가 뭘까요?
- 약한 참조(weak reference)는 참조 타입(reference type)에만 정의할 수 있습니다.
- 따라서, 만약 프로토콜을 만족하는 객체가 weak var이고 싶다면, 해당 프로토콜은 class - only protocol 이여야 합니다.
weak var를 사용할 경우, instance 참조 시 retain count가 늘어나지 않습니다. 따라서 아래와 같은 retain count를 가지게 됩니다.
- 위와 같은 상황에서는 ThirdVC가 dismiss 될 경우, retain count가 1이 되고, SecondVC가 dismiss가 되면서 SecondViewController의 retain count가 0이 되므로 SecondViewController 객체는 메모리 할당 해제가 됩니다.
- 그러면 자연스럽게 thirdVC 프로퍼티는 ThirdViewController를 참조하지 않게 되므로, ThirdViewController 객체의 retain count가 하나 빠지게 됩니다. 최종적으로 ThirdViewController 객체의 참조 값은 0이 되므로 메모리에서 할당 해제가 됩니다!
이와 같이 무의식적으로 delegate 변수를 weak로 선언하고 있지는 않은 지 확인해 보는 것이 좋을 거 같습니다! 항상 순환 참조가 발생하는 것은 아니기 때문이죠!
'iOS' 카테고리의 다른 글
[iOS] Core Location Unit Test하기(feat. WWDC18) (0) | 2023.07.23 |
---|---|
[iOS] 공식문서로 보는 Core Location (0) | 2023.07.16 |
[iOS] Diffabledatasource의 identifier는 왜 Hashable 해야 할까? (0) | 2023.06.08 |
[iOS] FileManager (0) | 2023.06.08 |
[iOS] NotificationCenter (0) | 2023.04.13 |