Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: "failable initializer 'init()' cannot override a non-failable initializer" vs. default parameters

If I declare

public class A: NSObject {
    public class X { }
    public init?(x: X? = nil) { }
}

all is fine. When using it like let a = A(), the initializer is called as expected.

Now, I'd like to have the nested class X private, and the parameterized init as well (has to be, of course). But a simple init?() should stay publicly available as it was before. So I write

public class B: NSObject {
    private class X { }
    private init?(x: X?) { }
    public convenience override init?() { self.init(x: nil) }
}

But this gives an error with the init?() initializer: failable initializer 'init()' cannot override a non-failable initializer with the overridden initializer being the public init() in NSObject.

How comes I can effectively declare an initializer A.init?() without the conflict but not B.init?()?

Bonus question: Why am I not allowed to override a non-failable initializer with a failable one? The opposite is legal: I can override a failable initializer with a non-failable, which requires using a forced super.init()! and thus introduces the risk of a runtime error. To me, letting the subclass have the failable initializer feels more sensible since an extension of functionality introduces more chance of failure. But maybe I am missing something here – explanation greatly appreciated.

like image 216
Stefan Avatar asked Jul 11 '16 16:07

Stefan


2 Answers

This is how I solved the problem for me:

I can declare

public convenience init?(_: Void) { self.init(x: nil) }

and use it like

let b = B(())

or even

let b = B()

— which is logical since its signature is (kind of) different, so no overriding here. Only using a Void parameter and omitting it in the call feels a bit strange… But the end justifies the means, I suppose. :-)

like image 181
Stefan Avatar answered Nov 02 '22 11:11

Stefan


After a bit of fiddling I think I understand. Let's consider a protocol requiring this initializer and a class implementing it:

protocol I {
    init()
}

class A : I {
    init() {}
}

This gives the error: "Initializer requirement 'init()' can only be satisfied by a required initializer in non-final class 'A'". This makes sense, as you could always declare a subclass of A that doesn't inherit that initializer:

class B : A {
    // init() is not inherited
    init(n: Int) {}
}

So we need to make our initializer in A required:

class A : I {
    required init() {}
}

Now if we look at the NSObject interface we can see that the initializer is not required:

public class NSObject : NSObjectProtocol {
    [...]
    public init()
    [...]
}

We can confirm this by subclassing it, adding a different initializer and trying to use the normal one:

class MyObject : NSObject {
    init(n: Int) {}
}

MyObject() // Error: Missing argument for parameter 'n:' in call

Now here comes the weird thing: We can extend NSObject to conform to the I protocol, even though it doesn't require this initializer:

extension NSObject : I {} // No error (!)

I honestly think this is either a bug or a requirement for ObjC interop to work (EDIT: It's a bug and already fixed in the latest version). This error shouldn't be possible:

extension I {
    static func get() -> Self { return Self() }
}

MyObject.get()
// Runtime error: use of unimplemented initializer 'init()' for class '__lldb_expr_248.MyObject'

Now to answer your actual question:

In your second code sample, the compiler is right in that you cannot override a non-failable with a failable initializer.

In the first one, you aren't actually overriding the initializer (no override keyword either), but instead declaring a new one by which the other one can't be inherited.

Now that I wrote this much I'm not even sure what the first part of my answer has to do with your question, but it's nice to find a bug anyways.

I suggest you to do this instead:

public convenience override init() { self.init(x: nil)! }

Also have a look at the Initialization section of the Swift reference.

like image 30
Kametrixom Avatar answered Nov 02 '22 10:11

Kametrixom