NotificationCenter.addObserver(forName:object:queue:using:) にインスタンスメソッドを渡すとリークする

これまで、NotificationCenterのaddObserverはSwiftでもaddObserver(_:selector:name:object:)で書いていたのだけど、ふと「あー@objc書きたくないな〜」と思った。

そこで単純にaddObserver(forName:object:queue:using:)に置き換えたのだけど、これでリークを作ってしまった。

ようは、元々Objective-Cセレクタを呼び出すこういうコードがあって

class TestViewController: UIViewController {
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        NotificationCenter.default.addObserver(self, selector: #selector(onAlpha(_:)), name: .myAppAlphaActionNotificaiton, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    @objc
    func onAlpha(_ notificaiton: Notification) {
        // any
    }
}

こんな感じでaddObserver(forName:object:queue:using:)に書き換えたところ、このViewControllerをNavigationControllerからpopしてもdeinitされなくなった。

class TestViewController: UIViewController {
    private var alphaNotificationHandle: AnyObject?

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        alphaNotificationHandle = NotificationCenter.default.addObserver(
            forName: .myAppAlphaActionNotificaiton,
            object: nil,
            queue: OperationQueue.main,
            using: onAlpha(_:)
        )
    }

    deinit {
        alphaNotificationHandle = nil
    }

    func onAlpha(_ notificaiton: Notification) {
        // any
    }
}

よく考えたら、そのonAlphaの参照を残すために強参照が作られてしまって解放されなくなるのが原因だとわかるけど単純に書き換えでオッケーだなって軽く思っていて痛い目を見た感じ。

こう書いてある箇所では問題なかった。

class TestViewController: UIViewController {
    private var alphaNotificationHandle: AnyObject?

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        alphaNotificationHandle = NotificationCenter.default.addObserver(
            forName: .myAppAlphaActionNotificaiton,
            object: nil,
            queue: OperationQueue.main
        ) {[weak self] notification in 
            self?.onAlpha(notification)
        }
    }

    deinit {
        alphaNotificationHandle = nil
    }

    func onAlpha(_ notificaiton: Notification) {
        // any
    }
}

addObserver(forName:object:queue:using:) のusingは、ブロックを渡すのみで運用した方が良さそう。同様に、パラメータが一緒だからといって、引数にインスタンスメソッドそのものを渡すのは避けておいた方がトラブル回避できて良さそうだなと思った。