Below is sample code from a playground. What I don't understand is why the b
variable in the subclass must be a var type and cannot be a let. Can someone help me understand?
class Base1 {
init() { }
}
class Sub1: Base1 {
let b: Int
override init() {
super.init()
}
convenience init(b: Int) {
self.b = b // Cannot assign to property: 'b' is a 'let' constant
self.init()
}
}
In Swift, designated initializers are used to set up all the stored properties. This is from the official documentation:
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.
Convenience initializer are secondary, and needs to call desginated initializer.
You can also define a convenience initializer to create an instance of that class for a specific use case or input value type. Create convenience initializers whenever a shortcut to a common initialization pattern will save time or make initialization of the class clearer in intent.
When you are trying your code, although the error message is not clear at this point, the issue is that you cannot use convenience init to initialize a stored property. You have to make b optional var to make it work.
class Sub1: Base1 {
var b: Int?
override init() {
super.init()
}
convenience init(b: Int) {
self.init()
self.b = b
}
}
Or better way is, as @Martin R suggested in one of the comments, to create another designated initializer and use convenience before overridden init, to give a default value:
class Sub1: Base1 {
let b: Int
init(b: Int) {
self.b = b
super.init()
}
convenience override init() {
self.init(b:5)
}
}
I think this has less to do with subclassing and more to do with the class initializers.
The error only tells half the story, changing b
from let
to var
would show other problems with this class:
Initializers must set an initial value for each stored property of the class. If you override init
, you must provide a default value for b
.
A convenience initializer must call a designated initializer before accessing self
This is how it looks (thanks to Martin R for the improvements suggested in the comments on how to maintain b
as a let
constant):
class Base1 {
init() { }
}
class Sub1: Base1 {
let b: Int
convenience override init() {
self.init(b: 5)
}
init(b: Int) {
self.b = b
super.init()
}
}
let one = Sub1(b: 10)
one.b // prints 10
let two = Sub1()
two.b // prints 5
First of all, imho, this has nothing with derived class, you could remove you subclassing and get the similar error.
Second, I think it is due to the "nature" of convenience initializers and their usage. They are "optional" (in usage) initializers, for example to provide shortcut way to initialize from some complex data (like other class or structure). Adding initialization of self.b
to convenience init in your sample wouldn't bring anything helpful, as you anyway would need to initialize b
in designated init:
class Sub1: Base1 {
let b: Int
// You must init b
// Or ger error: property 'self.b' not initialized ...
init(b: Int) {
self.b = b
super.init()
}
// Do you really need the code below now?
//convenience init(b: Int) {
// self.b = b
// self.init()
//}
}
Thus as designated init must initialize b
, the convenience initializer as your wrote it becomes meaningless.
Third, assume it would be allowed. So, as convenience initializer are delegated across not necessarily calling the designated one. Thus it would be possible to do something like that:
...
convenience init(b: Int) {
self.b = b
self.init(c: 10)
}
// later some crazy guy adds his own "convenience" initializer and points your to this one.
convenience init(c: Int) {
self.c = c
if c == 7 {
self.b = 11
}
self.init()
}
Now imagine that you are the "compiler" and tell me where the constant b
is set to it's constant value and what value?
Finally, in my understanding the correct usage could be like that:
class Base1 {
init() {}
}
class Sub1: Base1 {
let b: Int
init(b: Int) {
self.b = b
super.init()
}
// Convenience initializer providing default value
override convenience init() {
self.init(b: 7)
}
}
So, my concern is that I don't understand clearly what you really wanted to achieve by allowing let b
initialized in convenience init?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With