对于下面的代码,print("What happened") 会被执行多少次呢?

class View: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        print("What happened?")
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

}

class NavigationController: UINavigationController {

    let someView = View()

}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        _ = NavigationController(rootViewController: UIViewController())
    }

}

答案居然是 2 次!😳

并且第一次初始化的 someView 变成了一个泄漏的内存……

原因是因为在 UINavigationController.h 文件中,完全没有标注 NS_DESIGNATED_INITIALIZER。导致 Swift 认为 initWithRootViewController: 也是 designated initializer,在调用它时,会对 someView 进行初始化。然而 initWithRootViewController: 的实现中会调用 initWithNibName:bundle:initWithNibName:bundle: 也被 Swift 认为是 designated initializer(实际上也是!),所以会再次初始化 someView

可以通过重写 init(rootViewController:) 让它变成一个 convenience initializer 来修复:

class NavigationController: UINavigationController {

    let someView = View()

    convenience override init(rootViewController: UIViewController) {
        self.init(nibName: nil, bundle: nil)
        viewControllers = [rootViewController]
    }

}

或者让 Apple 给 UINavigationController.h 里加上 NS_DESIGNATED_INITIALIZER 标注。😆

总结

虽然这是一个在 UINavigationController 上出现的问题,但我们自己写的类也是很容易出现这个问题的。所以在写 Objective-C 代码时也要好好遵守 designated initializer 的规则呀!

PS: 如何正确书写 iOS 中的 Initializer