The inconsistent requirements for class inheritance

One of the rules for Automatic Initializer Inheritance is:

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.

This makes sense since convenience initializers are delegating initializers which means they contain a designated initializer from within their class that they have to be able to refer to.

For example:

class Eletronic {
    var manufacturedDate: Date
    init(date: Date) {
        self.manufacturedDate = date
    }

    convenience init() {
        self.init(date: Date())
    }
}

class TV: Eletronic {
    var displayType: String
    init(displayType: String, date: Date) {
        self.displayType = displayType
        super.init(date: date)
    }

    override init(date: Date) {
        self.displayType = "1080p"
        super.init(date: date)
    }
}

let tv = TV()

Now, Self was introduced in Swift 5.1 which allows us to take advantage of Swift's polymorphism. One of the rules for using it is including the modifier required . For example:

class Electronic {
    var manufacturedDate: Date
    required init(date: Date) {
        self.manufacturedDate = date
    }
    
    func getElectronic(date: Date) -> Self {
        return Self(date: date)
    }
}

class TV: Electronic {
    var displayType: String
    init(displayType: String, date: Date) {
        self.displayType = displayType
        super.init(date: date)
    }
    
    required init(date: Date) {
        self.displayType = "1080p"
        super.init(date: date)
    }
}

let tv = TV(date: Date())
let tv2 = tv.getElectronic(date: anotherDate)

This also makes sense since Self is referring to another element within its class, a designated initializer in the above example's case. You want to enforce your subclasses to have the initializers with the same function signatures they can refer to.

If I'm not mistaken, both cases above have to do with the requirement for inheritance, but the former simply asks for a tacit fulfillment of having all the initializers of the superclass whereas the latter requires the modifier required . I find it a bit confusing that Swift is inconsistent on this.

The purpose of required keyword is to make sure that the initializer is present in the class and all of its subclasses. Initializer inheritance is pretty much opt-in, which doesn't guarantee that any initializer will be present in the subclass since the subclasses can just use their own set of initializers. If you have something like this:

class Electronics {
  required init(required: Void) { }
  init(designated: Void) { }

  convenience init(convenience: Void) { }
}

In this case, the subclass can opt-in to inherit init(convenience:) by also inheriting init(designated), and are required to implement init(required:) (via inheritance).

class SimpleTV: Electronics {
  // Inherit
  // init(required:)
  // init(designated:)
  // init(convenience:)
}

The subclasses can also just create their own set of initializers, but still required to implement init(required:).

class WeirdTV: Electronics {
  required init(required: Void) { }

  init(otherDesignated: Void) { }
  init(yetAnotherDesignated: Void) { }
}

In both cases, subclasses will have init(required:), which is the point of the required keyword.

4 Likes

Thank you for your answer