Required inits + convenience inits + inheritance == conundrum

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)?

Answered by QuinceyMorris in 97351022

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)
  }
}
Accepted Answer

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)
  }
}

Regarding this specific set of requirements, this is what I came up with in a playground:

Ah, of course, that would work. D'oh, I'm not sure why I didn't consider that. Still not as nice as having the objects come in as non-optionals so the subclasses know exactly what they're getting, and still requires every subclass to implement a boilerplate convenience initializer that passes nil to the larger one, but still, it's much better than the workaround I'd been considering. Thanks.


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.

Well, the example I showed above is an extremely simplified case, and is by no means representative of the entire class hierarchy. With that said, there are plenty of things that force you to put 'required' on your initializers in Swift, most of them having to do with anything involving any kind of polymorphism. If you are conforming to a protocol that contains an initializer, that initializer has to be marked as 'required'. Similarly, if you call init on a dynamicType, that's only possible with 'required'. As it turns out, I've got both of these; the existing Objective-C hierarchy was a class cluster which I want to eventually convert to something using composition and the new "protocol-oriented programming" style, as I think it would resolve a few issues that the code currently has. However, since that would break compatibility with Objective-C, I can't do that until I can convert all the users of this class cluster, which is going to take a long time, so in the meantime I'm just trying to get these classes ported to Swift and get it temporarily working with a factory class method that can be called from Objective-C. Anyway, the relative value and/or virtue of that is unfortunately out of my hands, because whether going old-school or new, the Init Nazi which is the Swift compiler demands obedience, and in this case that means having 'required' on all the init methods. :-P


(yes, it makes sense because the initializers aren't inherited; I know, I know. I'm in a cranky mood today.)

Required inits + convenience inits + inheritance == conundrum
 
 
Q