델리게이트 패턴 사용 시 순환참조에 의한 메모리릭 발생

iOS 개발 시 자주 사용되는 Delegate 패턴은 순환 참조에 의한 메모리릭에 주의해야 합니다.
(순환참조 : https://outofbedlam.github.io/swift/2016/01/31/Swift-ARC-Closure-weakself/)

순환참조에 의한 메모리릭은 두 객체가 서로 잡고있어 하나가 사라지려고 할 때 다른 한 쪽이 잡고 있으면 사라지지 않는 현상입니다. 먼저 메모리릭을 발생시키는 아래 코드를 살펴봅시다.

protocol LeakedModelDelegate {
}

class LeakedModel {
    var delegate: LeakedModelDelegate?
    deinit {
        print(“deinit LeakedModel”)
    }
}

class LeakedViewController: UIViewController, LeakedModelDelegate {
    var model: LeakedModel?
    override func viewDidLoad() {
        super.viewDidLoad()
        model = LeakedModel()
        model?.delegate = self
    }
    deinit {
        print(“deinit LeakedViewController”)
    }
}

LeakedViewController의 viewDidLoad에서 LeakedModel을 생성하고, 생성된 LeakedModel의 delegate 프로퍼티에LeakedModelDelegate를 지정한 LeakedViewController의 인스턴스인 self를 지정했습니다. 생각없이 위와 같이 작성하고, LeakedViewController가 화면에서 사라질 때 LeakedModel로 사라질거라 생각할 수 있지만 delegate에 의해 만들어진 순환참조가 LeakedViewController도 LeakedModel도 해제되지 못하게 합니다. LeakedViewController가 화면에서 사라져도(네비게이션 컨트롤러에 의해 pop되도) LeakedModel과 LeakedViewController의 deinit이 호출되지 않습니다.

protocol ModelDelegate: class {
}

class Model {
    weak var delegate: ModelDelegate?
        deinit {
        print(“deinit Model”)
    }
}

class ViewController: UIViewController, ModelDelegate {
    var model: Model?
    override func viewDidLoad() {
        super.viewDidLoad()
        model = Model()
        model?.delegate = self
    }
    deinit {
        print(“deinit ViewController”)
    }
}

델리게이트 패턴 사용 시 순환참조에 의해 발생되는 메모리릭을 막으려면, 델리게이트 선언 시 사용되는 protocol을 class 타입으로 제한하고, 델리게이트 프로퍼티를 선언하며 weak을 지정해야 합니다.

스크린샷 2016-11-24 오전 11.07.10.png

스크린샷 2016-11-24 오전 11.48.47.png

이제 실행 후 ViewController가 화면에서 사라질 때 deinit이 잘 호출되는지 확인해 봅시다. 포스팅에 사용된 코드는 아래 git에서 확인할 수 있습니다.

https://github.com/GoodMorningCody/ViewControllerDelegateMemoryLeakExample