Ethan
Ethan

Reputation: 18874

The correct way to override an initializer in Swift 1.1

This used to work in Xcode 6.1 beta:

class MainViewController: NSViewController {
  convenience override init() {
    self.init(nibName: "MainView", bundle: nil)
  }
}

After I switch to 6.1 GM2, it doesn't compile. Looks like the issue is related to "failable initializers" introduced in Swift 1.1. I've tried convenience override init?(), convenience init?() and override init?(), neither worked.

So what's the correct way to override this kind of initializers as of today?

Upvotes: 13

Views: 5943

Answers (1)

rickster
rickster

Reputation: 126187

You're trying to implement init() — a non-failable initializer — by delegating to init?(nibName:bundle:), which is a failable initializer. This doesn't work: if the super.init call fails, you'd be left with a non-initialized instance, which Swift won't allow.

Or to put it another way, the result of using a failable initializer is an optional, and you can't use an optional in place of a non-optional value. And in the case of class initialization and inheritance, you can't substitute a non-optional self for an optional one — you can only delegate the setup of self's state to a different initializer.

Instead, you can remove optionality/failability with a little song and dance:

class MainViewController: NSViewController {
    override init!(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        // check state here and provide app-specific diagnostic if it's wrong
    }
    convenience override init() {
        self.init(nibName: "MainView", bundle: nil)
    }

    // need this, too, or the compiler will complain that it's missing
    required init?(coder: NSCoder) {
        fatalError("not implemented") // ...or an actual implementation
    }
}

An init! initializer produces an implicitly unwrapped optional (IUO) — just as an IUO type can be used to bridge between code that works with optional and non-optional values, an init! initializer can bridge between failable and non-failable initializers. You can't delegate from a non-failable initializer to a failable initializer, but you can delegate from a non-failable initializer to an init! initializer and from an init! initializer to a failable initializer.

Here, the NSViewController initializer you want to use is fully failable, so you override it with an init! initializer. Then, you can declare a non-failable convenience init that delegates to your new init! initializer.


We often tend to avoid IUOs, and by extension init! initializers, because we generally want to either explicitly allow for (and require handling) failure or explicitly disallow it. However, one of the strongest general use cases for IUOs and their kin is to turn conditions that are guaranteed only outside of your source code into assertions that your code can treat as infallible. IBOutlets are a great example of this — in your nib/storyboard you guarantee the state of your IBOutlet variables, but the compiler doesn't know about that — as are just about anything else to do with bundle resources.

This little delegation dance puts the burden of failure at a specific, easily debuggable place in your code — if the call from init() to super.init(nibName:bundle:) fails, your app will crash. But you can expect that call to fail only in very specific (and mostly at-development-time) conditions.

Upvotes: 20

Related Questions