结构体和类在创建实例的过程中需要进行一些初始化的工作,这个过程被称之为构造过程。同样,这些实例在使用完成之后需要做一些清除工作,这个过程被称之为析构过程。下面,我们来学习一下Swift中的构造和析构。
1、构造函数
结构体和类在实例化的过程中会调用init()方法,而这个方法被称之为构造函数。与Objective-C中的构造函数不同,Swift中构造函数没有返回值,可以重载。通常情况下,在定义一个类或者结构体时,不需要我们编写构造函数,系统会提供一个默认的构造函数:
class Person {
var age: Int = 0
var name: String?
lazy var stu: Student = Student()
}
struct Student {
var no: Int = 0
var sex: String?
}
// 实例化一个Person对象
let p = Person()
p.age = 20
p.name = "Lilei"
p.stu.no = 1701
p.stu.sex = "Male"
以前在Objective-C中,我们实例化一个类时,一般会明确调用alloc和init方法。比如说,创建一个UIView对象:
UIView *view = [[UIView alloc] init];
但是,在Swift中,如果要创建一个UIView对象,好像不用调用任何方法,直接使用类型加小括号就可以了:
let view = UIView()
实际上,在Swift中创建一个对象,仍然需要调用方法,而小括号就代表着方法的调用。在上面的代码中,我们实际上调用了系统默认的init()方法。以上面的Person类为例,它实际上应该是类似于下面这样的:
class Person {
var age: Int = 0
var name: String?
lazy var stu: Student = Student()
init() {
}
}
struct Student {
var no: Int = 0
var sex: String?
init() {
}
}
2、构造函数与存储属性的初始化
我们都知道,存储属性在定义的时候需要初始化,否则编译器会报错。但是实际上,除了可以在定义的时候初始化之外,还可以在构造函数中对其进行初始化:
class Person {
var age: Int
var name: String
lazy var stu: Student = Student(no: 0, sex: "Male")
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
struct Student {
var no: Int
var sex: String
init(no: Int, sex: String) {
self.no = no
self.sex = sex
}
}
// 在实例化的过程中,通过构造函数对它的属性进行初始化
let p = Person(age: 20, name: "LeBron")
p.stu.no = 1701
p.stu.sex = "Femal"
以上面的Person类为例,其属性age和name在定义的时候没有被初始化,但是在实例化的时候,我们通过构造函数分别给它们初始化为20和LeBron。构造函数只能初始化存储属性(不管常量还是变量都可以),不能初始化计算属性(因为计算属性不需要存储数据)和静态属性(因为静态属性跟具体的实例无关)。
3、构造函数重载
在了解什么是构造函数重载之前,我们先来了解一下什么叫做函数重载。函数重载就是指函数名(或者沿袭Objective-C中叫方法也行)相同,但是参数不同。参数不同又包括参数标签不同、参数个数不同和参数类型不同。Objective-C中没有方法重载的概念,因为在Objective-C中调用方法的本质是发送消息,如果方法名相同,系统在发送消息时,就不知道该把消息发送给谁。构造函数重载,就是指同一个类或者结构体中可以有多个init()函数,但是它们的参数不同。比如说:
// 类的构造函数
class Person {
var age: Int
var name: String
// 构造函数1
init() {
self.age = 0
self.name = ""
}
// 构造函数2
init(age: Int, name: String) {
self.age = age
self.name = name
}
// 构造函数3
init(A age: Int, N name: String) {
self.age = age
self.name = name
}
}
// 调用构造函数1
let p1 = Person()
p1.age = 33
p1.name = "James"
print("age = \(p1.age), name = \(p1.name)") // age = 33, name = James
// 调用构造函数2
let p2 = Person(age: 33, name: "Anthony")
print("age = \(p2.age), name = \(p2.name)") // age = 33, name = Anthony
// 调用构造函数3
let p3 = Person(A: 35, N: "Wade")
print("age = \(p3.age), name = \(p3.name)") // age = 35, name = Wade
上面的Person类中定义了3个构造函数,它们之间是重载的关系,在实例化的过程中,通过调用不同的构造函数来创建对象。
4、构造函数代理
有时候为了减少代码重复,在定义构造函数时,可以在一个构造函数中调用另外一个构造函数,这种情况被称之为构造函数代理。比如说:
class Person {
var age: Int
var name: String
// 构造函数1
convenience init() {
// 调用了构造函数2
self.init(age: 0, name: "")
}
// 构造函数2
init(age: Int, name: String) {
self.age = age
self.name = name
}
// 构造函数3
init(A age: Int, N name: String) {
self.age = age
self.name = name
}
}
在上面的代码中,我们在构造函数1中调用了构造函数2,这种情况就是构造函数代理。在结构体中,构造函数调用其它构造函数时,可以直接调用,前面不用加关键字convenience。但是,由于类具有继承关系,类里面的构造函数代理又相对复杂一点。
如果构造函数代理是发生在同一个类的内部,那么这种构造函数便称之为便利构造函数,比如说像上面那种情况就是便利构造函数;如果构造函数代理是发生在子类中,那么子类在构造过程中需要调用父类的构造函数,初始化父类的存储属性,这种构造函数称之为指定构造函数。比如说:
class Person {
var age: Int
var name: String
init() {
self.age = 0
self.name = ""
}
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
class Student: Person {
var no: Int
var height: Double
// 构造函数
init(age: Int, name: String, no: Int, height: Double) {
self.no = no
self.height = height
// 调用父类的构造函数,对父类中的属性进行初始化
super.init(age: age, name: name)
}
// 便利构造函数
convenience override init(age: Int, name: String) {
// 初始化no和height
self.init(age: age, name: name, no: 0, height: 0.0)
}
}
// 实例化Student
let stu = Student(age: 20, name: "Anthony", no: 1701, height: 1.72)
子类中构造函数的代理涉及到了继承,所以相关代码的解释我准备放在继承中进行。
5、析构函数
析构过程与构造过程相反,析构在实例释放的时候需要清除一些资源。析构过程会调用deinit函数。deinit函数又被称为析构函数,它没有返回值,没有参数,也不需要小括号。析构函数不能重载。析构函数的具体实例如下:
// 类的构造函数
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
convenience init(A age: Int, N name: String) {
self.init(age: age, name: name)
}
deinit {
self.age = 0
self.name = ""
}
}
var p: Person? = Person(age: 20, name: "James")
print("age = \(p!.age), name = \(p!.name)")
// 调用析构函数,释放资源
p = nil
析构函数的调用使得实例被赋值为nil,表示实例需要释放资源。p = nil是触发析构函数调用的条件。