Swift-构造方法

构造方法是为了使用某个类、结构体和枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。Swift构造方法使用init()方法

1
init(参数列表){ 初始化代码 }

与OC中的构造器不同,Swift的构造器无需返回值,他们主要的任务是保证新实例在第一次使用前完成正确的初始化。类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行清理内存的工作

注意点:

  • 在Swift中类/结构体/枚举都需要构造方法
  • 构造方法的作用仅仅是用于初始化属性,而不是分配内存,分配内存是系统帮我们做的
  • 构造方法是隐式调用的,通过类名称()形式创建一个对象就会隐式调用init()构造方法
  • 如果所有的存储属性都有默认值,可以不提供构造方法,系统会提供一个隐式的构造方法
  • 如果存储属性可以提供缺省,那么提倡大家使用设置缺省值得方式,这样可以简化代码(不用自定义构造法,不用写存储属性类型)

存储属性的初始赋值

  1. 类和结构体在实例创建是,必须为所有存储属性设置合适的初始值。
  2. 存储属性在构造器中赋值是,它们的值是被直接设置的,不会触发任何属性观测器

存储属性在构造器中的赋值流程:

  • 创建初始值
  • 在属性定义中指定默认属性值
  • 初始化实例,并调用init()方法

不带参数构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person {
var name:String = "daisuke"
var age:Int
/*
var age:Int
// Stored property 'age' without initial value prevents synthesized initializers
这样声明属性,会报这样的一个错误,必须有初始值。
当然如果你非要这样写,那么就要在init方法中给属性赋值
init() {
age = 10
}
*/


// var age:Int = 0
func description() ->String{
return "name = \(name) age = \(age)"
}
init() {
print("init")
age = 10
}
}
// 1.分配内存 2.初始化name和age 3.构造方法是隐式调用的
var person = Person()
print(person.description())
// init
// name = daisuke age = 10

带参数构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Person1 {
var name:String = "daisuke"
var age:Int
func description() ->String{
return "name = \(name) age = \(age)"
}
// init(name:String, age:Int) {
// self.name = name;
// self.age = age;
// }
// 构造方法对属性的顺序没有要求, 只要保证对象构造完时所有存储属性被初始化即可
init(age:Int,name:String) {
self.name = name;
self.age = age;
}

func run(name:String, age:Int) {
print("name is \(name) and age is \(age) running")
}

/*
内部和外部参数名
跟函数和方法相同,构造参数也存在一个在构造器内部使用的参数名称和一个在调用构造器时使用的外部参数名字
然而,构造器并不想函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器的参数名和类型类确定需要调用的构造器。
如果你在定义构造器时没有提供参数的外部名字,Swift会为每个构造器的参数自动生成一个跟内部名字相同的外部名

没有外部名称参数
如果你不希望在构造的某个参数提供外部名字,可以使用下划线_来显示描述他的外部名
*/

init(_ name:String, age:Int) {
self.name = name;
self.age = age;
}

}
//var person1 = Person1(name: "hello", age: 26)
var person1 = Person1(age: 26, name: "hello")
print(person1.description())
// name = hello age = 26

person1.run(name: "haha", age: 25)

var person2 = Person1("world", age: 45)
print(person2.description())
// name = world age = 45

常量属性&构造方法

常量存储属性只能通过缺省值或在构造方法中被修改,其他任何地方都不能修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person2 {
let name:String
var age:Int
init(name:String, age:Int) {
// name是尽管是let声明,可以在构造方法中修改
self.name = name
self.age = age
}
func description() ->String{
return "name = \(name) age = \(age)"
}
}

var person2 = Person2(name: "new name", age: 45)
print(person2.description())
person2.age = 77
// 常量存储属性初始化之后不允许被修改
//person2.name = "haha"
print(person2.description())

可选属性&构造方法

如果属性是可选类型,那么可选值存储属性可以不在构造方法中初始化,也就是说可选值在对象构造完毕后不用初始化,其实如果不对可选存储属性进行初始化,默认就是nil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Car {
let name:String
init(name:String) {
self.name = name
}
}

class Person3 {
let name:String
var age:Int
var car:Car?
// car是可选值类型,可以不需要再构造方法中初始化,默认赋值为nil
init(name:String, age:Int) {
self.name = name
self.age = age
}
func description() ->String{
return "name = \(name) age = \(age) car = \(String(describing: car))"
}
}

var person3 = Person3(name: "daisuke", age: 26)
print(person3.description())
// name = daisuke age = 26 car = nil

结构体构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Rect {
var width:Double = 0.0
var height:Double = 0.0
/*
此时既没有提供缺省值,也没有提供构造方法,但是编译通过
因为默认情况下,结构体会给结构体提供一个默认的成员逐一构造器
init(width:Double, height:Double) {
self.width = width
self.height = height
}
*/

init() {
self.width = 0.0
self.height = 0.0
}
}
var r = Rect()

注意:

  1. 在类中默认是没有逐一构造器的
  2. 如果在结构体中自定义了构造方法,那么系统不会生成默认的逐一构造器
  3. 如果给存储属性提供缺省值,系统还是会提供默认的逐一构造器
  4. 如果给存储属性提供缺省值,可以使用不带参数的方法初始化结构体

构造器代理

构造器代理:构造方法可以调用其他构造方法来完成实例的构造,称之为构造器代理(构造方法之间互相调用)
好处:减少构造方法之间的重复代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Rect1 {
var width:Double = 0.0
var height:Double = 0.0

init(width:Double, height:Double) {
self.width = width
self.height = height
}

init() {
// self.width = 0.0
// self.height = 0.0
// 调用其他构造器
self.init(width: 0.0, height: 0.0)
}
func show(){
print("width = \(width) height = \(height)")
}
}

var r1 = Rect1()
r1.show()
// width = 0.0 height = 0.0
var r2 = Rect1(width: 0.2, height: 0.3)
r2.show()
// width = 0.2 height = 0.3

构造器代理规则

值类型 类类型
不支持继承,所以构造器代理的过程相对简单,因为他们只能代理本身提供的其他构造器。你可以使用self.init在自定义的构造器中引用其他的属于相同值类型的构造器 他可以继承自其他类,这意味着类有责任保证其所有继承的存储属性在构造时也能正确初始化

继承与构造方法

指定构造方法&便利构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Person4 {
var name:String
var age:Int
// 指定构造方法都是init开头的
init(name:String, age:Int) {
self.name = name
self.age = age
}
// 被convenience关键字修饰的构造方法称之为便利构造器,通过调用它构造方法来初始化
// 反而言之,便利构造器中一定要调用其他构造方法初始化的,一定要出现self.init
// 否则会报错:self.init' isn't called on all paths before returning from initializer
convenience init() {
// self.name = "haha"
// self' used in property access 'name' before 'self.init' call
// 给属性赋值,在self.init之前会报错,在self.init之后就可以
self.init(name: "daisuke", age: 26)
self.name = "haha"
}
func description() ->String{
return "name = \(name) age = \(age)"
}

// =============================

// 类可以拥有多个构造方法
init(name:String) {
self.name = name
self.age = 0
// self.init()
// 不能在指定构造方法中调用便利构造方法,也就是说在指定构造方法中不能出现self.init。
}

convenience init(age:Int) {
// 可以在便利构造器中调用指定构造器
// self.init(name: "daisuke", age: 26)
// 可以在便利构造器中调用便利构造器
self.init()
}

// 便利构造器不能和指定构造器同名
// convenience init(name:String) {
//
// }
}

var person4 = Person4()
print(person4.description())
// name = haha age = 26
var person44 = Person4(age: 22)
print(person4.description())
// name = haha age = 26
派生类的构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person5 {
var name:String
// 指定构造方法
init(name:String) {
self.name = name;
}
// 便利构造方法
convenience init() {
self.init(name: "person")
}
}

class Son: Person5 {
var age:Int

init(age:Int) {
/*
注意:
1、默认情况构造方法不会被继承
2、基类的存储属性只能通过基类的构造方法初始化
3、初始化存储属性必须先初始化当前类在初始化父类
4、不能通过便利构造方法初始化父类,只能通过指定构造方法初始化父类
*/

self.age = age
super.init(name: "daisuke")
// super.init() Must call a designated initializer of the superclass 'Person5'
}
}
构造器间的调用规则
  1. 子类指定构造器必须调用其直接父类的“指定构造器”
  2. 便利构造器必须调用同类中的其他构造器(指定或便利)
  3. 便利构造器必须最终以调用一个指定构造器结束(无论调用是指定还是便利,最终肯定会调用一个指定构造器)

Tip:

  • 指定构造器总是向上代理(父类)
  • 便利构造器总是横向代理(当前类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Person5 {
var name:String
// 指定构造方法
init(name:String) {
self.name = name;
}
// 便利构造方法
convenience init() {
self.init(name: "person")
}
}

class Son1: Person5 {
var age:Int
init(age:Int) {
self.age = age
super.init(name: "daisuke")
}
convenience init() {
self.init(age: 26)
}
convenience init(name:String, age:Int) {
// 调用子类构造器一定能够初始化所有属性
// self.init(age: 26)

/*
报错:Convenience initializer for 'Son1' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')
便利构造器中只能通过self.init来初始化,不能使用super.init。
因为调用父类构造器不一定能完全初始化所有属性(子类特有的)
*/

// super.init(name: "daisuke")

self.init()
}
}
两段式构造

构造过程可以分为两个阶段:

  1. 确保当前类和父类所有存储属性都被初始化
  2. 做一些其他初始化操作

好处:

  1. 可以防止属性在被初始化之前访问
  2. 可以防止属性被另外一个构造器意外赋值

注意:构造器的初始化永远是在所有类的第一阶段初始化之后开始第二阶段

编译器安全检查:

  1. 必须先初始化子类特有属性,再向上代理父类指定构造方法初始化父类属性
  2. 只能在调用完父类指定构造器后才能访问父类属性
  3. 在便利构造器中,必须先调用同类其他构造方法后才能访问属性
  4. 第一阶段完成前不能访问父类属性,也不能引用self和调用任何实例方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Person5 {
var name:String
// 指定构造方法
init(name:String) {
self.name = name;
}
// 便利构造方法
convenience init() {
self.init(name: "person")
}
}

class Son2: Person5 {
var age:Int
init(age:Int) {
print("第一阶段")
// 对子类引入的属性初始化
self.age = age

/*
报错:'self' used in property access 'name' before 'super.init' call
因为调用self.name之前还没有对父类的name进行初始化,即便在这个地方修改,也会被后面的初始化语句覆盖
*/

// self.name = "daisuke"

// 对父类引入的属性进行初始化
super.init(name: "daisuke")

print("第二阶段")
if age > 26 {
self.name = "老油条"
}
}

func description(){
print("name:\(super.name), age:\(age)")
}
}

var son2 = Son2(age: 26)
son2.description()
//第一阶段
//第二阶段
//name:daisuke, age:26
重写构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Person5 {
var name:String
// 指定构造方法
init(name:String) {
self.name = name;
}
// 便利构造方法
convenience init() {
self.init(name: "person")
}
}

class Son3: Person5 {
var age:Int
init(age:Int) {
self.age = age
super.init(name: "daisuek")
}

// 如果子类中的构造器和父类一模一样,必须加上override关键字,便是重写父类方法。
// 在老版本的swift语言中是不需要override关键字的。新版本才需要
// override init(name: String) {
// self.age = 26
// super.init(name: name)
// }

// 将父类的指定构造器重写成一个便利构造器,也必须加上override关键字,表示重写父类方法
// convenience override init(name: String) {
// self.init(name: "daisuke")
// }

/*
Swift中便利构造方法不存在重写,如果加上override关键字,系统会去查找父类中有没有和便利构造方法一样的指定构造方法,有旧不报错,没有就报错
为什么便利构造器不能重写呢?因为便利构造器只能横向代理,只能调用当前类的其他构造方法或指定方法,不可能调用super,所以不存在重写
也就是说子类的便利构造方法不能直接访问父类的便利构造方法,所以不存在重写的概念
*/

// convenience override init() {
// self.init(name: "daisuke")
// }

}
构造方法的自动继承
  1. 如果子类中没有定义任何构造器,且子类中所有的存储属性都有缺省值,会继承父类中所有的构造方法(包括便利构造器)
  2. 如果子类只是重写了父类中的某些指定构造器,不管子类中的存储舒心是否有缺省值,都不会继承父类中的其他构造器
  3. 如果子类重写了父类中所有的指定构造器,不管子类中的存储属性是否有缺省值,都会同事继承父类中的所有便利构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Person6 {
var name:String
var age:Int
init(name:String, age:Int) {
self.name = name
self.age = age
}
init(name:String) {
self.name = name
self.age = 26
}
convenience init() {
self.init(name: "daisuke")
}
}

//class Son6: Person6 {
//
//}
// 如果子类中没有定义任何构造器, 且子类中所有的存储属性都有缺省值, 会继承父类中所有的构造方法(包括便利构造器)
// 父类的存储属性是由父类的构造器初始化, 子类的存储属性是由缺省值初始化的
//var son6 = Son6(name: "daisuke", age:88)
//var son66 = Son6(name: "daisuke")
//var son666 = Son6()

//class Son6: Person6 {
// var height:Double
// init(height:Double) {
// self.height = height
// super.init(name: "daisuke", age: 88)
// }
//
// override init(name: String, age: Int) {
// self.height = 99
// super.init(name: name, age: age)
// }
//}
// 如果子类中只是重写了父类中的某些指定构造器, 不管子类中的存储属性是否有缺省值, 都不会继承父类中的其它构造方法
//var son6 = Son6(name: "daisuke")


class Son6: Person6 {
var height:Double
override init(name: String) {
self.height = 99
super.init(name: name, age: 66)
}

override init(name: String, age: Int) {
self.height = 99
super.init(name: name, age: age)
}
}
// 如果子类重写了父类中所有的指定构造器, 不管子类中的存储属性是否有缺省值, 都会同时继承父类中的所有便利方法
var son6 = Son6()
required构造方法

只要在构造方法前面加上一个required关键字,那么所有的子类(后续子类)只要定义了构造方法都必须实现该构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Person7 {
var name:String
// 早期Swift版本中没有这个语法
required init(name:String) {
self.name = name
}
}

class Son7: Person7 {
var age:Int = 26
// 如果子类没有定义构造方法,可以不用重写

// 定义构造方法
init() {
self.age = 30
super.init(name: "daisuke")
}

// 定义了就会报错:'required' initializer 'init(name:)' must be provided by subclass of 'Person7'
required init(name: String) {
self.age = 33
super.init(name: name)
}
/*
1、如果子类自定义了构造器,就必须重写“必须构造器”,因为如果子类没有子类没有自定义任何构造器,默认会继承父类的构造器,所以不用重写
2、重写必须构造器不用加override关键字,因为系统看到required关键字就会自动查看父类
3、为什么重写了还需要加上required关键字,因为给后续子类都必须重写
*/

}
var son7 = Son7(name: "daisuke")

参考: