While trying to convert an older Objective-C program to Swift, I've run into a hitch.
So, let's say we've got a base class with a required initializer:
class Base {
required init(foo: Foo) { … }
}and then we have a subclass, but the subclass includes a new parameter in the init, specific to the subclass, and satisfies the requirement via a convenience initializer:
class Subclass: Base {
required init(foo: Foo, bar: Bar) {
...
super.init(foo: foo)
...
}
required convenience init(foo: Foo) {
// Do some calculation to determine a default value for ‘bar’ from the input of ‘foo’.
// Perhaps this calculation is quite a few lines of code.
let bar = ...
self.init(foo: foo, bar: bar)
}
}Now, that subclass gets subclassed once again:
class SubSubclass: Subclass {
required init(foo: Foo, bar: Bar) {
…
super.init(foo: Foo, bar: Bar)
...
}
required convenience init(foo: Foo) {
// Uh oh.
}
}The trouble here is that we’re required to implement init(foo:) per the base class’s interface, but we cannot use Subclass’s perfectly serviceable implementation, because convenience initializers cannot call super, and of course even if it could, super’s implementation could not be called because only designated initializers are able to be called via super. In Objective-C this would have been a non-issue, because SubSubclass would have just picked up Subclass’s implementation of the convenience initializer without complaining, but of course initializers in Swift are managed by Heinrich Himmler, so any time init methods are involved you're going to have a giant headache.
I understand what the problem is here; initializers in Swift aren't necessarily inherited, so the subclass has no way to know whether or not one of the superclass's convenience initializers will call an init that it actually implements. In this case, the convenience initializer does call a required init, but there's no way to express that. What I'm wondering is, is there any workaround for this that's more elegant than a) copy-and-pasting a large amount of code into the convenience initializer of every class that subclasses this object or b) creating a defaultBarWithFoo() class method for every subclass to call in the convenience initializer (and of course, have that class method polluting the class's public interface for callers to mistakenly invoke, since Swift has no "protected" access specifier)?
Regarding this specific set of requirements, this is what I came up with in a playground:
struct Foo { }
struct Bar { }
class Base {
let foo: Foo
required init(foo: Foo)
{ self.foo = foo }
}
class Subclass: Base {
let bar: Bar
required init (foo: Foo, bar: Bar?) {
if let bar = bar {
self.bar = bar
}
else {
self.bar = Bar () // etc
}
super.init(foo: foo)
}
convenience required init (foo: Foo) {
self.init(foo: foo, bar: nil)
}
}
class SubSubclass: Subclass {
required init (foo: Foo, bar: Bar?) {
super.init(foo: foo, bar: bar)
}
}However, I don't understand the need for 'required'. What's the virtue of using it at all? I can't see any value in this case. Without that requirement, I'd do this:
class Base {
let foo: Foo
init(foo: Foo)
{ self.foo = foo }
}
class Subclass: Base {
let bar: Bar
init (foo: Foo, bar: Bar? = nil) {
if let bar = bar {
self.bar = bar
}
else {
self.bar = Bar () // etc
}
super.init(foo: foo)
}
}
class SubSubclass: Subclass {
override init (foo: Foo, bar: Bar? = nil) {
// other stuff
super.init(foo: foo, bar: bar)
}
}