Home > Back-end >  Swift Convinience Init and Delegator
Swift Convinience Init and Delegator

Time:07-07

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.

  • Related