Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class does not implement its superclass's required members

From an Apple employee on the Developer Forums:

"A way to declare to the compiler and the built program that you really don't want to be NSCoding-compatible is to do something like this:"

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}

If you know you don't want to be NSCoding compliant, this is an option. I've taken this approach with a lot of my SpriteKit code, as I know I won't be loading it from a storyboard.


Another option you can take which works rather well is to implement the method as a convenience init, like so:

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}

Note the call to an initializer in self. This allows you to only have to use dummy values for the parameters, as opposed to all non-optional properties, while avoiding throwing a fatal error.


The third option of course is to implement the method while calling super, and initialize all of your non-optional properties. You should take this approach if the object is a view being loaded from a storyboard:

required init(coder aDecoder: NSCoder!) {
    foo = "some string"
    bar = 9001

    super.init(coder: aDecoder)
}

There are two absolutely crucial pieces of Swift-specific information that are missing from the existing answers which I think help clear this up completely.

  1. If a protocol specifies an initializer as a required method, that initializer must be marked using Swift's required keyword.
  2. Swift has a special set of inheritance rules regarding init methods.

The tl;dr is this:

If you implement any initializers, you are no longer inheriting any of the superclass's designated initializers.

The only initializers, if any, that you will inherit, are super class convenience initializers that point to a designated initializer which you happened to override.

So... ready for the long version?


Swift has a special set of inheritance rules regarding init methods.

I know this was the second of two points I made, but we can't understand the first point, or why the required keyword even exists until we understand this point. Once we understand this point, the other one becomes pretty obvious.

All of the information I cover in this section of this answer is from Apple's documentation found here.

From the Apple docs:

Unlike subclasses in Objective-C, Swift subclasses do not inherit their superclass initializers by default. Swift’s approach prevents a situation in which a simple initializer from a superclass is inherited by a more specialized subclass and is used to create a new instance of the subclass that is not fully or correctly initialized.

Emphasis mine.

So, straight from the Apple docs right there, we see that Swift subclasses will not always (and usually don't) inherit their superclass's init methods.

So, when do they inherit from their superclass?

There are two rules that define when a subclass inherits init methods from its parent. From the Apple docs:

Rule 1

If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.

Rule 2

If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

Rule 2 isn't particularly relevant to this conversation because SKSpriteNode's init(coder: NSCoder) is unlikely to be a convenience method.

So, your InfoBar class was inheriting the required initializer right up until the point that you added init(team: Team, size: CGSize).

If you were to have not provided this init method and instead made your InfoBar's added properties optional or provided them with default values, then you'd have still been inheriting SKSpriteNode's init(coder: NSCoder). However, when we added our own custom initializer, we stopped inheriting our superclass's designated initializers (and convenience initializers which didn't point to initializers we implemented).

So, as a simplistic example, I present this:

class Foo {
    var foo: String
    init(foo: String) {
        self.foo = foo
    }
}

class Bar: Foo {
    var bar: String
    init(foo: String, bar: String) {
        self.bar = bar
        super.init(foo: foo)
    }
}


let x = Bar(foo: "Foo")

Which presents the following error:

Missing argument for parameter 'bar' in call.

enter image description here

If this were Objective-C, it'd have no problem inheriting. If we initialized a Bar with initWithFoo: in Objective-C, the self.bar property would simply be nil. It's probably not great, but it's a perfectly valid state for the object to be in. It's not a perfectly valid state for the Swift object to be in. self.bar is not an optional and cannot be nil.

Again, the only way we inherit initializers is by not providing our own. So if we try to inherit by deleting Bar's init(foo: String, bar: String), as such:

class Bar: Foo {
    var bar: String
}

Now we're back to inheriting (sort of), but this won't compile... and the error message explains exactly why we don't inherit superclass init methods:

Issue: Class 'Bar' has no initializers

Fix-It: Stored property 'bar' without initializers prevents synthesized initializers

If we've added stored properties in our subclass, there's no possible Swift way to create a valid instance of our subclass with the superclass initializers which couldn't possibly know about our subclass's stored properties.


Okay, well, why do I have to implement init(coder: NSCoder) at all? Why is it required?

Swift's init methods may play by a special set of inheritance rules, but protocol conformance is still inherited down the chain. If a parent class conforms to a protocol, its subclasses must to conform to that protocol.

Ordinarily, this isn't a problem, because most protocols only require methods which don't play by special inheritance rules in Swift, so if you're inheriting from a class that conforms to a protocol, you're also inheriting all of the methods or properties that allow the class to satisfy protocol conformance.

However, remember, Swift's init methods play by a special set of rules and aren't always inherited. Because of this, a class that conforms to a protocol which requires special init methods (such as NSCoding) requires that the class mark those init methods as required.

Consider this example:

protocol InitProtocol {
    init(foo: Int)
}

class ConformingClass: InitProtocol {
    var foo: Int
    init(foo: Int) {
        self.foo = foo
    }
}

This doesn't compile. It generates the following warning:

Issue: Initializer requirement 'init(foo:)' can only be satisfied by a 'required' initializer in non-final class 'ConformingClass'

Fix-It: Insert required

It wants me to make the init(foo: Int) initializer required. I could also make it happy by making the class final (meaning the class can't be inherited from).

So, what happens if I subclass? From this point, if I subclass, I'm fine. If I add any initializers though, I am suddenly no longer inheriting init(foo:). This is problematic because now I'm no longer conforming to the InitProtocol. I can't subclass from a class that conforms to a protocol and then suddenly decide I no longer want to conform to that protocol. I've inherited protocol conformance, but because of the way Swift works with init method inheritance, I've not inherited part of what's required to conform to that protocol and I must implement it.


Okay, this all makes sense. But why can't I get a more helpful error message?

Arguably, the error message might be more clear or better if it specified that your class was no longer conforming to the inherited NSCoding protocol and that to fix it you need to implement init(coder: NSCoder). Sure.

But Xcode simply can't generate that message because that actually won't always be the actual problem with not implementing or inheriting a required method. There is at least one other reason to make init methods required besides protocol conformance, and that's factory methods.

If I want to write a proper factory method, I need to specify the return type to be Self (Swift's equivalent of Objective-C's instanceType). But in order to do this, I actually need to use a required initializer method.

class Box {
    var size: CGSize
    init(size: CGSize) {
        self.size = size
    }

    class func factory() -> Self {
        return self.init(size: CGSizeZero)
    }
}

This generates the error:

Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer

enter image description here

It's basically the same problem. If we subclass Box, our subclasses will inherit the class method factory. So we could call SubclassedBox.factory(). However, without the required keyword on the init(size:) method, Box's subclasses are not guaranteed to inherit the self.init(size:) that factory is calling.

So we must make that method required if we want a factory method like this, and that means if our class implements a method like this, we'll have a required initializer method and we'll run into the exact same problems you've run into here with the NSCoding protocol.


Ultimately, it all boils down to the basic understanding that Swift's initializers play by a slightly different set of inheritance rules which means you're not guaranteed to inherit initializers from your superclass. This happens because superclass initializers can't know about your new stored properties and they couldn't instantiate your object into a valid state. But, for various reasons, a superclass might mark an initializer as required. When it does, we can either employ one of the very specific scenarios by which we actually do inherit the required method, or we must implement it ourselves.

The main point here though is that if we're getting the error you see here, it means that your class isn't actually implementing the method at all.

As perhaps one final example to drill in the fact that Swift subclasses don't always inherit their parent's init methods (which I think is absolutely central to fully understanding this problem), consider this example:

class Foo {
    init(a: Int, b: Int, c: Int) {
        // do nothing
    }
}

class Bar: Foo {
    init(string: String) {
        super.init(a: 0, b: 1, c: 2)
        // do more nothing
    }
}

let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)

This fails to compile.

enter image description here

The error message it gives is a little misleading:

Extra argument 'b' in call

But the point is, Bar doesn't inherit any of Foo's init methods because it hasn't satisfied either of the two special cases for inheriting init methods from its parent class.

If this were Objective-C, we'd inherit that init with no problem, because Objective-C is perfectly happy not initializing objects' properties (though as a developer, you shouldn't have been happy with this). In Swift, this simply will not do. You can't have an invalid state, and inheriting superclass initializers can only lead to invalid object states.


Why has this issue arisen? Well, the plain fact is that it has always been important (i.e. in Objective-C, since the day I started programming Cocoa back in Mac OS X 10.0) to deal with initializers that your class is not prepared to handle. The docs have always been quite clear about your responsibilities in this regard. But how many of us bothered to fulfill them, completely and to the letter? Probably none of us! And the compiler did not enforce them; it was all purely conventional.

For example, in my Objective-C view controller subclass with this designated initializer:

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;

...it is crucial that we be passed an actual media item collection: the instance simply cannot come into existence without one. But I have written no "stopper" to prevent someone from initializing me with bare-bones init instead. I should have written one (actually, properly speaking, I should have written an implementation of initWithNibName:bundle:, the inherited designated initializer); but I was too lazy to bother, because I "knew" I would never incorrectly initialize my own class that way. This left a gaping hole. In Objective-C, someone can call bare-bones init, leaving my ivars uninitialized, and we are up the creek without a paddle.

Swift, wonderfully, saves me from myself in most cases. As soon as I translated this app into Swift, the whole problem went away. Swift effectively creates a stopper for me! If init(collection:MPMediaItemCollection) is the only designated initializer declared in my class, I can't be initialized by calling bare-bones init(). It's a miracle!

What's happened in seed 5 is merely that the compiler has realized that the miracle doesn't work in the case of init(coder:), because in theory an instance of this class could come from a nib, and the compiler can't prevent that — and when the nib loads, init(coder:) will be called. So the compiler makes you write the stopper explicitly. And quite right too.


add

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}