Swift: The rules of being required (Xcode 6 GM)


Regular subclassing

Before we consider the role of the required modifier, let's begin with a regular class that has an init() method
class MyClass {
    var str:String
    init(str:String) {
        self.str = str
    }
}
and consider what happens when we subclass that class and then create an instance of the subclass:
class MyClass {
    var str:String
    init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass {
   
}

var MySubClass(str:"Hello Swift")
You'll notice that the subclass instance inherits the init() method from the superclass.

An init() in the subclass

Now if we add an init() method to the subclass
class MyClass {
    var str:String
    init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass {
    override init(str:String) {
        super.init(str:str)
    }   
}

var MySubClass(str:"Hello Swift")
then the init() must not only use the override modifier to make clear this is what we are doing (i.e. overriding the init() of the superclass) but it must also forward the values passed to it up the chain to the init() of the superclass.

A change in situation

The situation changes if the subclass is not initialized with the same type as the superclass (or indeed if the init is not identical in its parameter names). We then don't need to override the init() method of the superclass, but we do still need to call the init() method of the superclass within the subclass:
class MyClass {
    var str:String
    init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass
{
    init(i:Int) {
        super.init(str:String(i))
    }
}

MySubClass(i: 10)

The required modifier

The rules change if we add the required modifier before the init() of the superclass:
class MyClass {
    var str:String
    required init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass
{
    init(i:Int) {
        super.init(str:String(i))
    }
     // fatal error, we've not included the required init()
}

MySubClass(i: 10)
Now there is a fatal error unless our subclass includes the initializer that has been designated as required, so let's fix that:
class MyClass {
    var str:String
    required init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass
{
    required init(str:String) {
        super.init(str: str)
    }

    init(i:Int) {
        super.init(str:String(i))
    }
     // OK
}

MySubClass(i: 10)
The required modifier acts here as a request that the init() be overridden from within the superclass and at the same time replaces the override modifier in the subclass (it therefore has two roles).

When required isn't required

In the above example, we needed to add required init() to the subclass, but if our subclass didn't have any initializers then we wouldn't need to add this. For example:
class MyClass {
    var str:String
    required init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass
{


}

MySubClass(str: "hello swift")
The compiler doesn't complain here. Swift knows to use the init() of the superclass, because the subclass doesn't have one. As Apple explains:
You do not have to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initialiser.  (Apple)

A required convenience

Apple states the required modifier can also be used with convenience initializers, and so following the logic above we should be able to do this:
class MyClass {
    var str:String
    
    required convenience init() {
        self.init(str:"default")
    }
    
    init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass
{

    override init(str:String) {
        super.init(str:str)
    }


}

var a = MySubClass().str
And then be able to write
class MyClass {
    var str:String
    
    required convenience init() {
        self.init(str:"default")
    }
    
    init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass
{
    required convenience init() {
        self.init(str:"subclass default")
    }

    override init(str:String) {
        super.init(str:str)
    }

}

without the compiler complaining. As indeed we are, but when you come to create an instance of the subclass using the convenience initializer in this way an error is then raised about the missing argument.
var a = MySubClass().str // error, missing argument for str

Further investigation of the convenience

On further investigation (and upon discovering this reddit comment) it becomes clear that a required convenience init() doesn't follow the same pattern as an ordinary required init().

Here's some working code to explain why:
class MyClass {
    var str:String
    
    
    required init(str:String) {
        self.str = str
    }
}

class MySubClass:MyClass
{
    
    init(a:String,b:String) {
        super.init(str:a+b)
    }
    
    required convenience init(str:String) {
        self.init(a:str, b:" from MySubClass")
    }
    
}


MySubClass(str:"Hello").str // "Hello from MySubClass"
As you can see here, what I'm doing is not adopting the required init() as the designated initializer but instead I have a different designated initializer and so make the required init() the convenience init() of the subclass instead.

The rules of being required

  1. the required modifier can be used only with class initialisers
  2. every subclass must repeat the use of the required modifier that begins in its superclass either as a designated or convenience init()
  3. subclasses can omit the required init() if it is clear that the superclass version will be called
  4. the required modifier acts as a request that the init() be overridden from within the superclass and at the same time replaces the override modifier in the subclass

Conclusion

The required convenience init() had me quite confused for a while. I ended up querying this on the Apple Dev Forums and was helped by @mofodox before finding the reddit comment cited above. This enabled me to answer my own Apple Dev Forum query and to also complete this post in full. I hope this helps others too.

Endorse on Coderwall

Comments