I was trying to look up the difference between a convenience init and a designated init and found this: https://stackoverflow.com/a/40093677/19449624 Now it has this code
struct Scene
{
var minutes = 0
}
class Movie {
var title: String
var author: String
var date: Int
var scenes: [Scene]
init(title: String, author: String, date: Int) {
self.title = title
self.author = author
self.date = date
scenes = [Scene]()
}
convenience init(title:String) {
self.init(title:title, author: "Unknown", date:2016)
}
func addPage(page: Scene) {
scenes.append(page)
}
}
var myMovie = Movie(title: "my title") // Using convenicence initializer
var otherMovie = Movie(title: "My Title", author: "My Author", date: 12)
But if I change this to a struct and make the method mutating and remove the convenience keyword this works too:
struct Movie {
var title: String
var author: String
var date: Int
var scenes: [Scene]
init(title: String, author: String, date: Int) {
self.title = title
self.author = author
self.date = date
scenes = [Scene]()
}
init(title:String) {
self.init(title:title, author: "Unknown", date:2016)
}
mutating func addPage(page: Scene) {
scenes.append(page)
}
}
Could anyone maybe explain why this is? I tried to do some research but still don't fully understand.
CodePudding user response:
From Apple's Swift iBook:
Class Inheritance and Initialization:
All of a class’s stored properties—including any properties the class inherits from its superclass—must be assigned an initial value during initialization.
Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value. These are known as designated initializers and convenience initializers. Designated Initializers and 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.
Classes tend to have very few designated initializers, and it’s quite common for a class to have only one. Designated initializers are “funnel” points through which initialization takes place, and through which the initialization process continues up the superclass chain.
Every class must have at least one designated initializer. In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass, as described in Automatic Initializer Inheritance below.
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 can also define a convenience initializer to create an instance of that class for a specific use case or input value type.
You don’t have to provide convenience initializers if your class doesn’t require them. Create convenience initializers whenever a shortcut to a common initialization pattern will save time or make initialization of the class clearer in intent.”
Excerpt From: Apple Inc. “The Swift Programming Language (Swift 5.5).” Apple Books. https://books.apple.com/us/book/the-swift-programming-language-swift-5-6/id881256329
Note that the above only applies to class types (not value types like structs.)
CodePudding user response:
Consider the following classes:
public class Base {
public init() {
print("Base.init")
}
public convenience init(convenienceLevel: Int) {
print("Base.init(convenienceLevel: \(convenienceLevel))")
self.init()
}
}
public class Derived: Base {
public override init() {
print("Derived.init")
super.init()
}
}
Now let's run this code:
let d = Derived(convenienceLevel: 1)
Does this compile? If so, what does it print? You should think about that for a moment before reading on.
.
.
.
.
It does compile, because the Derived
type inherits the convenience init
from Base
. Here's what it prints:
Base.init(convenienceLevel: 1)
Derived.init
Base.init
Notice that even though we called the convenience init
defined in Base
, that convenience init
called the (designed) init
defined in Derived
.
Swift behaves this way for compatibility with Objective-C.
Because Swift wants to be compatible with Objective-C in this way, but also wants to make various safety guarantees about initialization, it requires you to specify which initializers are convenience
and which are designated (by not being convenience
), and restricts how initializers can call each other.
However, none of this applies to struct
, enum
, or actor
types, because struct
, enum
, and actor
types do not support inheritance. Since these types do not support inheritance, there is no possibility of a base type's init
calling an init
overridden by a subtype. So Swift can make the safe initialization guarantees without distinguishing between convenience
and designated initializers for these types.