Swift construction process


The construction process is the preparation process for using an instance of a class, structure or enumeration type. This process includes setting initial values ​​for each property in the instance and performing necessary preparation and initialization tasks.

Swift constructor uses init() method.

Unlike constructors in Objective-C, Swift's constructors do not need to return a value. Their main task is to ensure that new instances are properly initialized before they are used for the first time.

Class instances can also perform the work of cleaning up memory before the class instance is released by defining a deinitializer.



Initial assignment of stored attributes

When creating instances of classes and structures, appropriate initial values ​​must be set for all stored attributes. .

When stored properties are assigned in the constructor, their values ​​are set directly and no property observers are triggered.

The storage property assignment process in the constructor:

  • Create the initial value.

  • Specify default attribute values ​​in attribute definitions.

  • Initialize the instance and call the init() method.


Constructor

The constructor is called when creating a new instance of a specific type. Its simplest form is similar to an instance method without any parameters, named after the keyword init.

Glossary

init()
{
    // 实例化后执行的代码
}

Example

The following structure defines a constructor init without parameters, and initializes the values ​​of the stored attributes length and breadth to 6. and 12:

struct rectangle {
    var length: Double
    var breadth: Double
    init() {
        length = 6
        breadth = 12
    }
}
var area = rectangle()
print("矩形面积为 \(area.length*area.breadth)")

The execution output of the above program is:

矩形面积为 72.0

Default attribute value

We can set the initial value for the stored attribute in the constructor; Likewise, you can set a default value for a property when it is declared.

Using default values ​​can make your constructor simpler and clearer, and the type of the property can be automatically deduced from the default value.

In the following example, we set the default value for the attribute when declaring it:

struct rectangle {
	// 设置默认值
    var length = 6
    var breadth = 12
}
var area = rectangle()
print("矩形的面积为 \(area.length*area.breadth)")

The output result of the execution of the above program is:

矩形面积为 72

Construction parameters

You can provide construction parameters when defining the constructor init(), as shown below:

struct Rectangle {
    var length: Double
    var breadth: Double
    var area: Double
    
    init(fromLength length: Double, fromBreadth breadth: Double) {
        self.length = length
        self.breadth = breadth
        area = length * breadth
    }
    
    init(fromLeng leng: Double, fromBread bread: Double) {
        self.length = leng
        self.breadth = bread
        area = leng * bread
    }
}

let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面积为: \(ar.area)")

let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面积为: \(are.area)")

The output result of the above program execution is:

面积为: 72.0
面积为: 432.0

Internal and external parameters Name

The same as function and method parameters, construction parameters also have a parameter name used inside the constructor and an external parameter name used when calling the constructor.

However, constructors do not have a distinguishable name before the parentheses like functions and methods. Therefore, when calling a constructor, the constructor to be called is mainly determined by the parameter name and type in the constructor.

If you do not provide external names for parameters when defining a constructor, Swift will automatically generate an external name for each constructor parameter that is the same as the internal name.

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

print("red 值为: \(magenta.red)")
print("green 值为: \(magenta.green)")
print("blue 值为: \(magenta.blue)")

// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let halfGray = Color(white: 0.5)
print("red 值为: \(halfGray.red)")
print("green 值为: \(halfGray.green)")
print("blue 值为: \(halfGray.blue)")

The execution output of the above program is:

red 值为: 1.0
green 值为: 0.0
blue 值为: 1.0
red 值为: 0.5
green 值为: 0.5
blue 值为: 0.5

No external name parameter

If you do not want to provide an external name for a parameter of the constructor, you can use underscores _ to display the external name describing it.

struct Rectangle {
    var length: Double
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    //不提供外部名字
    init(_ area: Double) {
        length = area
    }
}

// 调用不提供外部名字
let rectarea = Rectangle(180.0)
print("面积为: \(rectarea.length)")

// 调用不提供外部名字
let rearea = Rectangle(370.0)
print("面积为: \(rearea.length)")

// 调用不提供外部名字
let recarea = Rectangle(110.0)
print("面积为: \(recarea.length)")

The execution output of the above program is:

面积为: 180.0
面积为: 370.0
面积为: 110.0

Optional attribute type

If your customized type contains a stored attribute that logically allows a null value, you need to define it as an optional type (optional type) Select attribute type).

When a stored property is declared optional, it will be automatically initialized to empty nil.

struct Rectangle {
    var length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")

let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")

let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")

The execution output of the above program is:

面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)

Modify constant attributes during the construction process

As long as the value of the constant can be determined before the end of the construction process, you can Modify the value of a constant property at any point during construction.

For a class instance, its constant attributes can only be modified during the construction process of the class that defines it; they cannot be modified in subclasses.

Although the length attribute is now a constant, we can still set its value in the constructor of its class:

struct Rectangle {
    let length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")

let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")

let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")

The output of the above program execution is:

面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)

Default constructor

The default constructor will simply create an instance with all property values ​​set to default values:

In the following example, all properties in the ShoppingListItem class have default values. , and it is a base class without a parent class, it will automatically obtain a default constructor that can set default values ​​for all properties

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()


print("名字为: \(item.name)")
print("数理为: \(item.quantity)")
print("是否付款: \(item.purchased)")

The output result of the above program execution is:

名字为: nil
数理为: 1
是否付款: false

Structure Member-by-member initializer

If structures provide default values ​​for all stored properties and do not provide custom initializers themselves, they can automatically obtain a member-by-member initializer.

When we call the member-by-member constructor, we pass the parameter name that is the same as the member attribute name to complete the initial assignment of the member attributes.

The following example defines a structure Rectangle, which contains two attributes length and breadth. Swift can automatically deduce the type Double of these two properties based on their initial assignments of 100.0 and 200.0.

struct Rectangle {
    var length = 100.0, breadth = 200.0
}
let area = Rectangle(length: 24.0, breadth: 32.0)

print("矩形的面积: \(area.length)")
print("矩形的面积: \(area.breadth)")

Since both stored properties have default values, the structure Rectangle automatically obtains a member-by-member constructor init(width:height:). You can use this to create new instances of Rectangle.

The execution output of the above program is:

名字为: nil
矩形的面积: 24.0
矩形的面积: 32.0

Constructor proxy of value type

The constructor can complete part of the construction process of the instance by calling other constructors. . This process is called constructor delegation, and it reduces code duplication across multiple constructors.

In the following example, the Rect structure calls the construction process of Size and Point:

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}


// origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size 结构体初始值: \(basicRect.size.width, basicRect.size.height) ")
print("Rect 结构体初始值: \(basicRect.origin.x, basicRect.origin.y) ")

// 将origin和size的参数值赋给对应的存储型属性
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))

print("Size 结构体初始值: \(originRect.size.width, originRect.size.height) ")
print("Rect 结构体初始值: \(originRect.origin.x, originRect.origin.y) ")


//先通过center和size的值计算出origin的坐标。
//然后再调用(或代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))

print("Size 结构体初始值: \(centerRect.size.width, centerRect.size.height) ")
print("Rect 结构体初始值: \(centerRect.origin.x, centerRect.origin.y) ")

The execution output of the above program is:

Size 结构体初始值: (0.0, 0.0) 
Rect 结构体初始值: (0.0, 0.0) 
Size 结构体初始值: (5.0, 5.0) 
Rect 结构体初始值: (2.0, 2.0) 
Size 结构体初始值: (3.0, 3.0) 
Rect 结构体初始值: (2.5, 2.5)

Constructor proxy rules

Value typeClass type
does not support inheritance, so the process of constructor proxies is relatively simple because they only Can proxy to other constructors provided by itself. You can use self.init to reference other initializers of the same value type in your custom constructor. It can inherit from other classes, which means that the class is responsible for ensuring that all its inherited stored properties are initialized correctly during construction.

Class inheritance and construction process

Swift provides two types of class constructors to ensure that stored properties in all class instances can obtain initial values. They are designated constructors and Convenience constructor.

##The most important initializer in a classThe minor, auxiliary constructor in the class initializes all the properties provided in the class, and calls the constructor of the parent class up according to the parent class chain to realize the parent class. Class initialization. Convenience initializers can be defined to call specified constructors in the same class and provide default values ​​for their parameters. You can also define convenience initializers to create an instance for a special purpose or with specific inputs. Every class must have at least one designated initializerProvide convenience initializers for classes only when necessary
Init(parameters) {
    statements
}
convenience init(parameters) {
      statements
}

Specified constructor instance

class mainClass {
    var no1 : Int // 局部存储变量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}
class subClass : mainClass {
    var no2 : Int // 新的子类存储变量
    init(no1 : Int, no2 : Int) {
        self.no2 = no2 // 初始化
        super.init(no1:no1) // 初始化超类
    }
}

let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)

print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")

The output result of the above program execution is:

res 为: 10
res 为: 10
res 为: 20

Convenience constructor instance

class mainClass {
    var no1 : Int // 局部存储变量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}

class subClass : mainClass {
    var no2 : Int
    init(no1 : Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 便利方法只需要一个参数
    override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)

print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")

The output result of the above program execution is:

res 为: 20
res2 为: 30
res2 为: 50

Inheritance and overloading of constructors

Subclasses in Swift will not inherit the constructor of the parent class by default.

The constructor of the parent class is only inherited when it is certain and safe.

When you override a parent class designated constructor, you need to write the override modifier.

class SuperClass {
    var corners = 4
    var description: String {
        return "\(corners) 边"
    }
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")

class SubClass: SuperClass {
    override init() {  //重载构造器
        super.init()
        corners = 5
    }
}

let subClass = SubClass()
print("五角型: \(subClass.description)")

The execution output of the above program is:

矩形: 4 边
五角型: 5 边

Specified constructor and convenience constructor instances

The following examples will show the designated constructor and convenience constructor in operation Inheritance of constructors and automatic constructors.

It defines a class hierarchy containing two classes, MainClass and SubClass, and will demonstrate how their constructors interact.

class MainClass {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[匿名]")
    }
}
let main = MainClass(name: "php")
print("MainClass 名字为: \(main.name)")

let main2 = MainClass()
print("没有对应名字: \(main2.name)")

class SubClass: MainClass {
    var count: Int
    init(name: String, count: Int) {
        self.count = count
        super.init(name: name)
    }
    
    override convenience init(name: String) {
        self.init(name: name, count: 1)
    }
}

let sub = SubClass(name: "php")
print("MainClass 名字为: \(sub.name)")

let sub2 = SubClass(name: "php", count: 3)
print("count 变量: \(sub2.count)")

The execution output of the above program is:

MainClass 名字为: php
没有对应名字: [匿名]
MainClass 名字为: php
count 变量: 3

Failable constructor of the class

If an object of a class, structure or enumeration type, in It is possible to fail during the construction of itself, so define a failable initializer for it.

Possible reasons for variable initialization failure include:

  • Invalid parameter value is passed in.

  • A required external resource is missing.

  • Specific conditions are not met.

In order to properly handle the situation that may fail during this construction process.

You can add one or more failable initializers to the definition of a class, structure, or enumeration type. The syntax is to add a question mark (init?) after the init keyword.

Example

In the following example, a structure named Animal is defined, which has a constant attribute of type String named species.

At the same time, the structure also defines a failable constructor with a String type parameter species. This failable initializer is used to check whether the incoming parameter is an empty string. If it is an empty string, the failable initializer fails to construct the object, otherwise it succeeds.

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

//通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功
// someCreature 的类型是 Animal? 而不是 Animal
let someCreature = Animal(species: "长颈鹿")

// 打印 "动物初始化为长颈鹿"
if let giraffe = someCreature {
    print("动物初始化为\(giraffe.species)")
}

The execution output of the above program is:

动物初始化为长颈鹿

Failable constructor of enumeration type

You can construct a failable constructor with one or more parameters Container to obtain specific enumeration members of an enumeration type.

Example

In the following example, an enumeration type named TemperatureUnit is defined. It contains three possible enumeration members (Kelvin, Celsius, and Fahrenheit) and a failable initializer used to find the enumeration member corresponding to the Character value:

enum TemperatureUnit {
	// 开尔文,摄氏,华氏
    case Kelvin, Celsius, Fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}


let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("这是一个已定义的温度单位,所以初始化成功。")
}

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("这不是一个已定义的温度单位,所以初始化失败。")
}

The above program execution output results For:

这是一个已定义的温度单位,所以初始化成功。
这不是一个已定义的温度单位,所以初始化失败。

Failable initializer of class

Failable initializer of value type (such as structure or enumeration type), when and where the construction failure is triggered There are no restrictions on behavior.

However, the failable initializer of a class can only trigger failure behavior after all class attributes are initialized and proxy calls between constructors between all classes have occurred.

Example

In the following example, a class named StudRecord is defined. Because the studname attribute is a constant, once the StudRecord class is constructed successfully, the studname attribute must have a non-nil value.

class StudRecord {
    let studname: String!
    init?(studname: String) {
        self.studname = studname
        if studname.isEmpty { return nil }
    }
}
if let stname = StudRecord(studname: "失败构造器") {
    print("模块为 \(stname.studname)")
}

The above program execution output is:

模块为 失败构造器

Overriding a failable constructor

Just like other constructors, you can also use failable constructors of subclasses The constructor overrides the base class's failable initializer.

Alternatively, you can also override a base class's failable initializer with a subclass's non-failable initializer.

You can override a failable initializer with a non-failable initializer, but the reverse doesn't work.

A non-failable initializer can never delegate a call to a failable initializer.

Examples

The following examples describe failable and non-failable constructors:

class Planet {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[No Planets]")
    }
}
let plName = Planet(name: "Mercury")
print("行星的名字是: \(plName.name)")

let noplName = Planet()
print("没有这个名字的行星: \(noplName.name)")

class planets: Planet {
    var count: Int
    
    init(name: String, count: Int) {
        self.count = count
        super.init(name: name)
    }
    
    override convenience init(name: String) {
        self.init(name: name, count: 1)
    }
}

The execution output of the above program is:

行星的名字是: Mercury
没有这个名字的行星: [No Planets]

Failable initializer init!

Generally speaking, we define a failable initializer by adding a question mark (init?) after the init keyword, but you can also use it by adding an exclamation mark after init way to define a failable initializer (init!). The example is as follows:

struct StudRecord {
    let stname: String
    
    init!(stname: String) {
        if stname.isEmpty {return nil }
        self.stname = stname
    }
}

let stmark = StudRecord(stname: "php")
if let name = stmark {
    print("指定了学生名")
}

let blankname = StudRecord(stname: "")
if blankname == nil {
    print("学生名为空")
}

The output result of the execution of the above program is:

指定了学生名
学生名为空
Specified initializerConvenience initializer