13

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.

Stefan
  • 1,036
  • 10
  • 32
  • `override` implies a method with the same signature in the superclass. However there is no `init?` in `NSObject` – vadian Jul 11 '16 at 16:53
  • @vadian: Yes, but omitting the `override` produces the error message `overriding declaration requires an 'override' keyword`, so it counts as overriding anyway. Fix-it is inserting `override`, giving the other error. Moreover, I am allowed to override a failable init with a non-failable, but not vice versa. – Stefan Jul 11 '16 at 16:55

2 Answers2

13

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. :-)

Stefan
  • 1,036
  • 10
  • 32
  • Thanks, I found this very useful for a Realm db model where I had just a couple of managed object classes that should never be initialised with empty contents, only with a full list of values. With this form I could still call the Realm Object constructor init(value: Any) as I could use the value label to distinguish the superclass initialiser from my failable one – Rocket Garden Nov 27 '16 at 11:13
2

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.

Kametrixom
  • 14,673
  • 7
  • 45
  • 62
  • Thank you for your detailed answer. The underlying problem is that the initializer of my subclass **can** fail since there are external resources involved (`NSCoding`…), so it is (kind of) necessary to test if the initializer was successful. But, please have a look at my "PS" addendum using a `Void` parameter. – Stefan Jul 11 '16 at 17:14
  • Note that the bug your describe above (`protocol I { init() }` followed by `extension NSObject : I { }`) _does_ yield an error in Swift 3.0-dev (_error: initializer requirement '`init()`' can only be satisfied by a `required` initializer in non-final class '`NSObject`'_), so I guess its been fixed. – dfrib Jul 12 '16 at 11:00