In this blog post, I am elaborating on why defining an open class in Swift with only an internal designated initializer is pointless. I am looking particularly at the use case of subclassing that class outside its module.
Designated Initializers vs. Convenience Initializers
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
Every class must have at least one designated initializer.
init(parameters) {
statements
}
Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You don’t have to provide convenience initializers if your class doesn’t require them.
convenience init(parameters) {
statements
}
The Question
// Module "Experiment"
open class Superclass {
internal init(param1: String, param2: String) {}
public convenience init(params: [String]) {
self.init(param1: "", param2: "")
}
}
Can Superclass
be subclassed outside of its module?
The short answer
No
The explanation
At a first glance, it may look like it should be possible because the class has open
access level.
Open access applies only to classes and class members, which differs from public access by allowing code outside the module to subclass and override. Marking a class as open explicitly indicates that you’ve considered the impact of code from other modules using that class as a superclass and that you’ve designed your class’s code accordingly.
But the class has only a single designated initializer which has access level internal
internal
enables entities to be used within any source file from their defining module, but not in any source file outside of that module. You typically use internal access when defining an app’s or a framework’s internal structure.
Source: Swift Language Guide - Access Control
So calling the designated initializer of the superclass will not work :(
import Experiment
public class Subclass: Superclass {}
func test() {
// ERROR: Subclass' cannot be constructed because it has no accessible initializers
let subclass = Subclass(param1: "", param2: "")
}
What about calling the public convenience initializer from the superclass?
import Experiment
public class Subclass: Superclass {}
func test() {
// ERROR: Subclass' cannot be constructed because it has no accessible initializers
let subclass = Subclass(params: [])
}
Nope :(
Swift subclasses don’t inherit their superclass initializers by default.
However, superclass initializers are automatically inherited if certain conditions are met.
Rule 1: If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 1 probably means it automatically inherits all the public
and open
superclass's designated initializers my example has none.
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.
Source: Swift Language Guide - Initialization
Rule 2 doesn't matter here as well. We cannot provide an implementation of the superclass's designated initializer because it's internal
.
Marina Gornostaeva asks the Swift team if the behavior is a bug or could be explained better through documentation. I am looking forward to their response.
What about adding a designated initializer in the subclass and calling the superclass's convenience initializer?
Not possible here because Swift requires that a designated initializer must call a designated initializer from its immediate superclass.
Conclussion
Question: Can you subclass an open
Swift class outside of its module if that class has a single, internal
designated initializer?
Answer: No
You should add a public
designated initializer to your open
class as a module developer. Otherwise it is not possible to subclass it from outside of its module.
The alternative is to remove the open
access level to make the intention clear that the class cannot be subclassed.