Mandatory init override in Swift UINavigationContr

2020-08-15 06:24发布

问题:

I'm currently subclassing a UINavigationController for a framework that serves a view controller flow (in a way, like UIImagePickerController do)

Here's an example of my implementation, reduced to be as simple as possible, that can be run in a playground.

import UIKit

public class MyNavigationController: UINavigationController {

    public var anyVar: Int?

    public init(anyVar: Int) {
        let viewController = UIViewController()
        super.init(rootViewController: viewController)

        self.anyVar = anyVar
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

let navigationController = MyNavigationController(anyVar: 42)

The last line is crashing, with a EXC_BAD_INSTRUCTION. When I run in Xcode, it's tells me at runtime that init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) was missing.

And if I override the method:

public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

...everything works well: you can try with your own playground.

I can't understand why. It doesn't sounds logic to me.

The UIViewController documentation says:

If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you, and specify nil for both of this methods arguments.)

But my init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) override gets called, from super.init(rootViewController: viewController) initialization!
Without overriding it, I guess the UIViewController's init(nibName:bundle:) should be called, but not.

I still cannot understand why overriding the method and calling super make the program works better. IMO, overriding a method while only calling super.thisMethod is totally useless, it only adds a method call in the call stack.

I must missing something essentials about Swift init methods, but I can't figure out what.

回答1:

This is happening because of the way how Swift inherits initializers. If you don't declare any of initializers in current class the compiler will inherit all of the initializers from the parent class. But if you override/add new initializers (and you do it with init(anyVar:)) Swift will not automatically inherit initializers from parent classes, so they are not accessible from subclass which is leading to runtime crash.

If you are interested in reasons beyond this behavior you can check out Intermediate Swift section and WWDC 2014 (somewhere around 34-minute mark they are talking about initializers inheritance)



回答2:

You can assign root after you initiated super class

public class MyNavigationController: UINavigationController {

    public var anyVar: Int?

    public init(anyVar: Int) {
        super.init(nibName: nil, bundle: nil)
        viewControllers = [UIViewController()]

        self.anyVar = anyVar
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}