17.3 打破强引用循环
打一个比方,强引用循环就像是两个人在吵架,每个人都顾及面子,都不肯示弱,都希望对方服个软,于是这场吵架就永远不会停止。如果想让吵架停下来,就需要让一个人主动示弱,服个软,吵架就会停止了。
打破强引用循环方法与停止吵架是类似的,我们在声明一个对象的属性时,让它具有能够“主动示弱”的能力,当遇到强引用循环问题的时候,不保持强引用。
Swift 提供了两种办法来解决强引用循环问题:弱引用(weak reference)和无主引用(unowned reference)。
17.3.1 弱引用
弱引用允许循环引用中的一个对象不采用强引用方式引用另外一个对象,这样就不会引起强引用循环问题。弱引用适合于引用对象可以没有值的情况,因为弱引用可以没有值,我们必须将每一个弱引用声明为可选类型,使用关键字Weak
声明为弱引用。
例如17.2节中介绍的人力资源管理系统,Employee
的dept
属性关联到Department
,Department
的manager
(部门领导)属性关联到Employee
。如果我们规定的业务需求是一个员工可以没有部门(刚刚入职),即Employee
的dept
(所在部门)属性可以为nil
。一个部门也可以暂时没有领导,即Department
的manager
(部门领导)属性也可以为nil
。
示例代码如下:
class Employee { ①
var no : Int
var name : String
var job : String
var salary : Double
var dept : Department? ②
init(no : Int, name: String, job : String, salary : Double) {
self.no = no
self.name = name
self.job = job
self.salary = salary
println("员工\(name) 已经构造成功。")
}
deinit {
println("员工\(name) 已经析构成功。")
}
}
class Department { ③
var no : Int = 0
var name : String = ""
weak var manager : Employee? ④
init(no : Int, name: String) {
self.no = no
self.name = name
println("部门\(name) 已经构造成功。")
}
deinit {
println("部门\(name) 已经析构成功。")
}
}
var emp: Employee?
var dept: Department?
emp = Employee(no: 7698, name: "Blake", job :"Salesman", salary : 1600)
dept = Department(no : 30, name: "Sales")
emp!.dept = dept ⑤
dept!.manager = emp ⑥
emp = nil ⑦
dept = nil ⑧
上述代码第①行定义了员工类Employee
,第②行代码var dept : Department?
声明所在部门的属性,它的类型是Department
可选类型。第③行代码定义了部门类Department
,第④行代码weak var manager : Employee?
声明部门领导的属性,它的类型是Employee
可选类型,使用关键字weak
声明为弱引用。
第⑤行代码emp!.dept = dept
建立强引用关系,第⑥行代码dept!.manager = emp
建立弱引用关系。如图17-7所示,其中的关系②是弱引用关系。
图 17-7 emp
与dept
对象之间建立关系
如果我们通过第⑦行代码emp = nil
和第⑧行代码dept = nil
断开引用关系,如图17-8所示,由于没有指向Employee
对象的强引用,所以Employee
对象会被释放。①号强引用关系也会被打破,Department
对象被释放。
图 17-8 emp
和dept
弱引用断开
17.3.2 无主引用
无主引用与弱引用一样,允许循环引用中的一个对象不采用强引用方式引用另外一个对象,这样就不会引起强引用循环问题。无主引用适用于引用对象永远有值的情况,它总是被定义为非可选类型,使用关键字unowned
表示这是一个无主引用。
例如17.2节中介绍的人力资源管理系统,Employee
的dept
属性关联到Department
,Department
的manager
(部门领导)属性关联到Employee
。如果我们规定的业务需求是一个员工可以没有部分(刚刚入职),即Employee
的dept
(所在部门)属性可以为nil
,一个部分不能没有领导,Department
的manager
(部门领导)属性是不能为nil
的。如图17-9所示,其中Employee
的dept
声明中有问号“?
”,说明是可选类型,也就是可以为nil
。而Department
的manager
声明中没有问号“?
”,说明是非可选类型,不可以为nil
。
图 17-9 Employee
与Department
关联关系
示例代码如下:
class Employee { ①
var no : Int
var name : String
var job : String
var salary : Double
var dept : Department? ②
init(no : Int, name: String, job : String, salary : Double) {
self.no = no
self.name = name
self.job = job
self.salary = salary
println("员工\(name) 已经构造成功。")
}
deinit {
println("员工\(name) 已经析构成功。")
}
}
class Department { ③
var no : Int = 0
var name : String = ""
unowned var manager : Employee ④
init(no : Int, name: String, manager : Employee) {
self.no = no
self.name = name
self.manager = manager
println("部门\(name) 已经构造成功。")
}
deinit {
println("部门\(name) 已经析构成功。")
}
}
var emp: Employee?
emp = Employee(no: 7698, name: "Blake", job :"Salesman", salary : 1600) ⑤
emp!.dept = Department(no : 30, name: "Sales", manager : emp!) ⑥
emp = nil ⑦
上述代码第①行定义了员工类Employee
,第②行代码var dept : Department?
声明所在部门的属性,它的类型是Department
可选类型。第③行代码定义了部门类Department
,第④行代码unowned var manager : Employee
声明部门领导的属性,它的类型是Employee
类型,不能为nil
,使用unowned
关键字声明为无主引用。
第⑤行代码创建Employee
对象,第⑥行代码给Employee
对象的dept
属性赋值,如图17-10所示,这里实例化Department
对象并没有像上一节一样把Department
对象的引用赋值给一个变量,而是直接把Department
对象赋值给Employee
对象的dept
属性。由于没有Department
对象的引用变量,我们不能通过给引用变量赋值为nil
的方式来释放对象,它的释放依赖于Employee
对象的释放,这就是无主引用的特点。
图 17-10 emp
与dept
对象之间建立关系
第⑦行代码通过emp = nil
语句断开强引用关系。如图17-11所示,由于没有指向Employee
对象的强引用,所以Employee
对象会被释放。然后①号强引用关系也被打破,Department
对象被释放。
图 17-11 emp
和dept
无主引用断开
另外,上述代码第②行的可选类型可以声明为如下形式:
var dept : Department!
这样当我们引用该对象时候可以采用隐式拆封。隐式拆封表达式是emp!.dept.name
,但如果不使用隐式拆封,表达式形式是emp!.dept!.name
。使用可选类型隐式拆封可以使代码变得更加简洁,引用它的时候省略了感叹号(!
)。