Hi all,
recently I stumbled upon an interesting and slightly confusing aspect of our
favorite language.
I had defined a Cache class to conform to NSCoding and inherit from NSObject.
My class’s initializer was declared init?(entries: [Entry]? = nil) at first,
giving an option to pre-populate it when called from the NSCoding-conformant
initializer but also simply start from scratch in my program. Failability is a
valuable feature since the class depends on external resources that could be
unavailable. Of course there are other means to model this, but a failable
initializer is the most elegant one, I think.
Later on, I decided to make the Entry class private to my Cache class.
Now I had to split the initializer in a private init?(entries: [Entry]?) and an
internal or public convenience init?().
I’ll abstract a bit now:
First version:
public class A: NSObject {
public class X { }
public init?(x: X? = nil) { }
}
— all good. I can use it like let a = A() in my program.
Second version:
public class B: NSObject {
private class X { }
private init?(x: X?) { }
public convenience override init?() { self.init(x: nil) } // ERROR
}
Now the compiler complains "failable initializer 'init()' cannot override a
non-failable initializer" with the overridden initializer being the public
init() in NSObject. This is the same in Swift 2 and 3.
Omitting the override does not work, the compiler now says “overriding
declaration requires an 'override’ keyword", so it does count as overriding
anyway. Suggested Fix-it is inserting override, giving the other error.
How comes I can effectively declare an initializer A.init?() without the
conflict but not B.init?() ?
And: Why at all 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 even requires using a forced super.init()! and thus
introduces the risk of a runtime error. I always have a bad feeling when I use
!, so I try to avoid it whenever possible. I would even accept to get a
compiler warning ;-)
To me, letting the subclass have the failable initializer feels more natural
since an extension of functionality introduces more chance of failure. But
maybe I am missing something here – explanation greatly appreciated.
But I found a workaround. My class now looks like this:
public class C: NSObject {
private class X { }
private init?(x: X?) { }
public convenience init?(_: Void) { self.init(x: nil) } // NO
ERROR
}
Now I can use it like let c = C(()) or even let c = C() – which is exactly what
I intended at first.
The fact that the new declaration does not generate an error message seems
(kind of) legal since it (somehow) differs from the superclass initializer's
signature. Nevertheless, using a nameless Void parameter at all and then even
omitting it completely in the 2nd version of the call feels a bit strange… But
the end justifies the means, I suppose. Elegance is almost preserved.
What do you think?
Sincerely,
Stefan
PS: I already posted this question on Stack Overflow
<http://stackoverflow.com/q/38311365/1950945>, but for the more philosophical
aspects / underpinnings, it is not the right medium — you are obliged to ask a
concrete question there (How to?, not Why?).
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users