前言
- 与 OC 一样,Swift 中也存在构造和析构过程。不同的是,OC 中的构造方法和析构方法只是普通的方法,而 Swift 中构造器和析构器是一种特殊的结构。
1、构造器
在 Swift 中,类或者结构体在初始化时必须保证它们中包含的所有属性都被初始化,构造器用来初始化类或者结构体中的属性,在形式上构造器和方法很像,但是它并不是一个方法。
- 声明构造器不需要
func
关键字。 - 和 OC 类似,在 Swift 中使用
init
表义构在器。 - 在一个类或者结构体中可以有多个构造器,所有的构造器都叫
init
,系统通过重载的特性来判断使用那个构造器。 - 在参数命名上可以使用和属性相同的名称做对应,如果构造器中参数的名称和属性的名称相同,则使用
self
关键字标示属性。 在类中 Swift 提供三种构造器来做初始化:指定构造器、便利构造器和可失败构造器。
// 指定构造器 init(参数名1: 参数类型, 参数名2: 参数类型, ...) { statements }
// 便利构造器 convenience init(参数名1: 参数类型, 参数名2: 参数类型, ...) { statements }
// 可失败构造器 init?(参数名1: 参数类型, 参数名2: 参数类型, ...) { statements }
- 声明构造器不需要
在某些情况下系统会自动生成一个构造器。
- 如果是在某个类中,当类中的所有属性都有初始值,并且类中没有定义构造器时,就会得到一个没有参数的构造器
init()
。 - 如果是在结构体中,当类中的所有属性都有初始值,并且结构体中没有定义构造器时,就会得到一个默认将所有属性作为参数的构造器。
- 如果是在某个类中,当类中的所有属性都有初始值,并且类中没有定义构造器时,就会得到一个没有参数的构造器
使用构造器时注意事项。
- 在任何构造器完成时,必须保证所有的属性都被初始化了,即便可选型属性的值是
nil
,也算它有值。 - 调用类中的方法和属性必须在初始化完成之后才能进行。
- 在任何构造器完成时,必须保证所有的属性都被初始化了,即便可选型属性的值是
在 Swift 中采用继承式初始化方式。
- 父类和子类的构造器进行交互时,是通过各自的指定构造器进行交互的。
- 便捷构造器只关心本类中的其它构造器,而不关心父类或者子类的构造器。
- 如果在构造器前加上
required
关键字,那么这个类的子类就必须实现它的这个构造器。
1.1 指定构造器
指定构造器包含系统自动生成的构造器和除便利构造器及可失败构造器之外的所有构造器,是默认的初始化方法。
指定构造器必须在
init
中调用父类的指定构造器,而不能调用其自身的其它构造器。init(参数名1: 参数类型, 参数名2: 参数类型, ...) { statements }
如果没有在类中实现任何指定构造器,那么将继承父类中的所有指定构造器。
// 父类 class Transport { var scope = "" init() {} // 无参数指定构造器 init(str: String) { // 有一个参数的指定构造器 self.scope = str } }
// 子类 class Car: Transport { // 没有在类中实现任何指定构造器 }
// 使用 let myCar = Car() // 使用了父类中的无参数构造器 let myNewCar = Car(str: "ludi") // 使用父类中有一个参数的构造器
一旦在子类中创建了自己的指定构造器,将不能再使用父类中的构造器,并且子类中的指定构造器声明中需要调用父类中的某个指定构造器。
// 父类 class Transport { var scope = "" init() {} // 无参数指定构造器 init(str: String) { // 有一个参数的指定构造器 self.scope = str } }
// 子类 class Car: Transport { var wheel = "pulisitong" init(scope: String, wheel: String) { // 创建自己的指定构造器 super.init() // 指定构造器必须调用父类的指定构造器 self.scope = scope self.wheel = wheel } }
// 使用 let myCar = Car(scope: "ludi", wheel: "miqilin") // 此时不能使用父类中无参或者有一个参数的构造器
1.2 便利构造器
调用其它构造器的构造器就叫便利构造器。
- 便利构造器必须调用一个其它的构造器。
- 便利构造器必须直接或者间接调用指定构造器之后才能访问其它值,它还可以通过调用其它便利构造器来间接调用指定构造器。
如果一个便利构造器想要调用指定构造器,则它必须并且只能调用本类中的指定构造器,而不能调任何父类的构造器。
convenience init(参数名1: 参数类型, 参数名2: 参数类型, ...) { statements }
如果子类中重写了父类中所有的指定构造器,那么将继承父类中所有的便利构造器。
// 父类 class Transport { var scope = "" init() {} // 无参数指定构造器 init(str: String) { // 有一个参数的指定构造器 self.scope = str } convenience init(scope: String) { // 便利构造器 self.init(str: scope) // 调用自身类的指定构造器 } }
// 子类 class Car: Transport { override init() { // 重写父类指定构造器 super.init() // 指定构造器必须调用父类的指定构造器 } override init(str: String) { // 重写父类指定构造器 super.init() // 指定构造器必须调用父类的指定构造器 self.scope = "Car" + str } }
// 使用 let myCar = Car(scope: "ludi") // 调用父类的便利构在器
1.3 可失败构造器
有一些初始化方法允许失败,并且返回
nil
。可失败构造器的定义中
init
后面跟着一个?
。init?(参数名1: 参数类型, 参数名2: 参数类型, ...) { statements }
通常面对这种可失败构造器建议使用
if-let
或guard-let-else
结构,如果初始化成功就执行操作,否则就做其它操作。if-let
结构if let image = UIImage(named: "test") { // 执行与 image 有关的代码 } else { // 执行与 image 无关的代码 }
guard-let-else
结构guard let image = UIImage(named: "test") else { // 执行与 image 无关的代码 } // 执行与 image 有关的代码
1.4 匿名构造器
有些时候某种对象的子类我们只创建一次,没有必要专门写一个构造器。
使用闭包的形式创建一个匿名的构造器,闭包后需要加 (),用来告诉 Swift 需要立刻执行此闭包,否则会把闭包本身作为值赋给了属性。
let button: UIButton = { let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) button.backgroundColor = UIColor.white return button }()
使用其它对象方法创建一个匿名的构造器。
let someArray = ["1", "2", "3"] let someString = someArray.joined(separator: ", ") print(someString) // 1, 2, 3
2、析构器
和构造器对应,Swift 还提供了析构器,用于释放对象。
- 析构器只适用于类。
- 一个类中只能定义一个析构器。
析构器的定义和构造器很像,使用关键字
deinit
,并且析构器没有参数。deinit() { // 执行析构过程 }
在把对象设置为
nil
时,系统会自动调用析构器。deinit { self.conn.close() self.conn = nil }
由于 Swift 中引入了自动引用计数(ARC),因此不需要读者手动管理内存,但在使用一些自己的资源的时候,需要使用析构器。
- 比如我要建立一个数据库访问类,在初始化时打开链接,如果程序退出,连接不释放,资源就会浪费。
值类型没有析构是因为值类型不会出现 “共享”,每个值类型只有一个拥有者。
- 比如某个方法,值类型会在其拥有者被系统回收时一同回收,我们不需要在意值类型的生命周期。