Swift protocol required initializer

In Swift, why protocol initializers must be defined as required in conforming types?

You may have asked yourself “why either the conforming types must define initializers of protocols as ‘required‘ or they must be final”. I will tell you. That is due to preserving the dynamic features of the Swift language.

If you do not add required modifier to the initializer and your class is not final you will encounter the following error:

Initializer requirement ‘init(color:)’ can only be satisfied by a ‘required’ initializer in non-final class ‘Rectangle’

Swift error

In Swift, using dynamic types, we can instantiate objects at runtime. You use this feature of the Swift normally in the following areas:

  • register function to register a custom TableViewCell with the UITableView or the similar approach for UICollectionView.
  • decode function by JSONDecoder to decode JSON data and instantiating an object of the registered type.
  • When your code needs to access static members of a type using one of its instances.

As you know, the ‘required‘ modifier in front of the initializers means all subclasses must have the same initializers. To see what would happen if the mandatory required rule did not exist, look at the following example code. Shape is a simple protocol that has a simple for of init in its definition.

protocol Shape {
 
    var color: String { get set }
    init(color: String)
}

Rectangle is a class that conforms to the Shape protocol, but imagine there was not ‘required init‘ rule is Swift language.

class Rectangle: Shape {
     var color: String
     var width: Int
     var height: Int
     
     init(color: String) {
         self.color = color
         self.width = 0
         self.height = 0
     }
     
     init(color: String, width: Int, height: Int) {
         self.color = color
         self.width = width
         self.height = height
     }
 }

Here is a function that can instantiate objects of all classes that conform to the Shape protocol dynamically at runtime.

func shapeBuilder(documentType: Shape.Type, color: String) -> Shape {
    return documentType.init(color: color)
}

So we can call the following code perfectly:

let rectangle = shapeBuilder(documentType: Rectangle.self, color: "Gray")

So far so good. Now suppose there is a square class that subclasses from the Rectangle, but since we imagined the ‘required init‘ is not necessary, the Rectangle class does not have the Shape protocol initializer.

class Square: Rectangle {
    
    init(color: String, size: Int) {
        super.init(color: color, width: size, height: size)
    }
}

We should be able to pass the Square class to the shapeBuilder function too. Because Square superclass, Rectangle, conforms to Shape protocol. But we know that the shapeBuilder uses a version of the init() which is not supported by the Square. So the following code will not work.

let square = shapeBuilder(documentType: Square.self, color: "Red")

Therefore, we realize why Swift obligate us to implement initializer of protocols as ‘required‘. It is for supporting dynamic initialization feature of the Swift. There is also another alternative. Defining the conforming class as final. As you know the final classes could not have any subclasses, so there would be no such a problem at all.

To summarize, the actual code that really works is as below:

protocol Shape {
    
    var color: String { get set }
    init(color: String)
}

class Rectangle: Shape, CustomStringConvertible {
    
    var color: String
    var width: Int
    var height: Int
    
    init(color: String, width: Int, height: Int) {
        self.color = color
        self.width = width
        self.height = height
    }
    
    required init(color: String) {
        self.width = 0
        self.height = 0
        self.color = color
    }
    
    var description: String {
        "Rectangle in \(color)"
    }
}

class Square: Rectangle {
    
    init(color: String, size: Int) {
        super.init(color: color, width: size, height: size)
    }
    
    required init(color: String) {
        super.init(color: color, width: 0, height: 0)
    }
    
    override var description: String {
        "Square in \(color)"
    }
}

func shapeBuilder(documentType: Shape.Type, color: String) -> Shape {
    return documentType.init(color: color)
}

let rectangle = shapeBuilder(documentType: Rectangle.self, color: "Gray")
let square = shapeBuilder(documentType: Square.self, color: "Red")

print(rectangle)
print(square)

Posted

in

by