The Correct Way to Override an Initializer in Swift 1.1

The correct way to override an initializer in Swift 1.1

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.

In Swift, why do I need to override this initialiser in order to make my own convenience initialiser?

Yes, this is almost certainly a bug. Filing it with Apple is probably a good idea.

Some ObjC APIs have a weird case of importing their factory methods to Swift only as "sort-of" initializers; you can see this is the case for UIStoryboard because the init(...) signature has a return type:

init(name: String, bundle storyboardBundleOrNil: NSBundle?) -> UIStoryboard
^^^^^^^^^^^^^^^

You can't declare an init with a return type in your own Swift code — this is just an artifact of the ObjC-to-Swift importer. And when you have a class that imports like this, you get stuck in a situation where you can't write a subclass init that calls the designated initializer (because as far as Swift knows there is no designated initializer) and you can't call the factory method either. So, yeah, sounds like a bug.

Note that even if this gets fixed you'll probably still need to override init(name:bundle:) before calling it from your own convenience init(). I'd expect the proper Swift API for this class to have a failable initializer (because you can specify a name or bundle that doesn't exist on disk). If you want to implement a non-failing init() in terms of a failable init?(...), you have to delegate twice through an init!(...), as seen in this answer.

Overriding init method in Swift for Objective-C class with naming conflict

I faced the same error, fixed it by changing the override method with:

required override init() {
super.init(withClass: "Item")
}

Since the init method is required to receive a string argument.
<<(NSString *)classname;>>

Also changed the init method to:

init(className: String){
super.init(withClass: className)
}

Hope it helps.

Initializer does not override a designated initializer from its superclass

My solution is a quick fix, but I think is easier than what Apple purposes on the the Release Notes. For more information search for 19775924 http://adcdownload.apple.com//Developer_Tools/Xcode_6.3_beta_3/Xcode_6.3_beta_3_Release_Notes.pdf here. What Apple says is that you create an Objective-C file and extend it (having to add it to the header files and all) and it's on "Known Issues in Xcode 6.3 beta 3", so I think is easy to do what I did:

This is how I fixed it for UIButton:

class CustomButton : UIButton {
init() {
super.init(frame: CGRectZero)
}

required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

And this is one of my ViewControllers (remove public if not needed):

public class GenericViewController: UIViewController {
public init() {
super.init(nibName: nil, bundle: nil)
}

required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I don't use IB so I also have UIView, because I do separate the view from the viewController (remove public if not needed):

public class GenericMenuView: UIView {
public init() {
super.init(frame: CGRectZero)
}

public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I need this specially in views because I have a setupViews method that I override in all subclasses that is called on the init. And using AutoLayout I don't need any frames (so I don't override the init with the frame parameter).

So it seems you have to drop override. Oh! and be sure to not call self.init() or the class is never initialized (and it crashes after some internal timeout).

What does the exclamation mark mean for a swift initializer?

It is failable initializer, introduced in Swift 1.1 (with Xcode 6.1)

From Apple Developer:

The init! Failable Initializer


You typically define a failable initializer that creates an optional
instance of the appropriate type by placing a question mark after the
init keyword (init?). Alternatively, you can define a failable
initializer that creates an implicitly unwrapped optional instance of
the appropriate type. Do this by placing an exclamation mark after the
init keyword (init!) instead of a question mark.

You can delegate from init? to init! and vice versa, and you can
override init? with init! and vice versa. You can also delegate from
init to init!, although doing so will trigger an assertion if the
init! initializer causes initialization to fail.

(emphasis mine)

NSFontAttributedString worked before XCode 6.1

Xcode 6.1 comes with Swift 1.1 that supports constructors that can fail. UIFont initialisation can fail and return nil. Also use string: when creating NSAttributedString:

if let font = UIFont(name: "Voyage", size: 20.0) {
let timeFont = [NSFontAttributeName:font]
var attrString3 = NSAttributedString(string: "(Time)", attributes : timeFont)
}

Re-initialize a lazy initialized variable in Swift

lazy is explicitly for one-time only initialization. The model you want to adopt is probably just an initialize-on-demand model:

var aClient:Client {
if(_aClient == nil) {
_aClient = Client(ClientSession.shared())
}
return _aClient!
}

var _aClient:Client?

Now whenever _aClient is nil, it will be initialized and returned. It can be reinitialized by setting _aClient = nil



Related Topics



Leave a reply



Submit