17.3 打破强引用循环

打一个比方,强引用循环就像是两个人在吵架,每个人都顾及面子,都不肯示弱,都希望对方服个软,于是这场吵架就永远不会停止。如果想让吵架停下来,就需要让一个人主动示弱,服个软,吵架就会停止了。

打破强引用循环方法与停止吵架是类似的,我们在声明一个对象的属性时,让它具有能够“主动示弱”的能力,当遇到强引用循环问题的时候,不保持强引用。

Swift 提供了两种办法来解决强引用循环问题:弱引用(weak reference)和无主引用(unowned reference)。

17.3.1 弱引用

弱引用允许循环引用中的一个对象不采用强引用方式引用另外一个对象,这样就不会引起强引用循环问题。弱引用适合于引用对象可以没有值的情况,因为弱引用可以没有值,我们必须将每一个弱引用声明为可选类型,使用关键字Weak声明为弱引用。

例如17.2节中介绍的人力资源管理系统,Employeedept属性关联到DepartmentDepartmentmanager(部门领导)属性关联到Employee。如果我们规定的业务需求是一个员工可以没有部门(刚刚入职),即Employeedept(所在部门)属性可以为nil。一个部门也可以暂时没有领导,即Departmentmanager(部门领导)属性也可以为nil

示例代码如下:

  1. class Employee {
  2. var no : Int
  3. var name : String
  4. var job : String
  5. var salary : Double
  6. var dept : Department?
  7. init(no : Int, name: String, job : String, salary : Double) {
  8. self.no = no
  9. self.name = name
  10. self.job = job
  11. self.salary = salary
  12. println("员工\(name) 已经构造成功。")
  13. }
  14. deinit {
  15. println("员工\(name) 已经析构成功。")
  16. }
  17. }
  18. class Department {
  19. var no : Int = 0
  20. var name : String = ""
  21. weak var manager : Employee?
  22. init(no : Int, name: String) {
  23. self.no = no
  24. self.name = name
  25. println("部门\(name) 已经构造成功。")
  26. }
  27. deinit {
  28. println("部门\(name) 已经析构成功。")
  29. }
  30. }
  31. var emp: Employee?
  32. var dept: Department?
  33. emp = Employee(no: 7698, name: "Blake", job :"Salesman", salary : 1600)
  34. dept = Department(no : 30, name: "Sales")
  35. emp!.dept = dept
  36. dept!.manager = emp
  37. emp = nil
  38. dept = nil

上述代码第①行定义了员工类Employee,第②行代码var dept : Department?声明所在部门的属性,它的类型是Department可选类型。第③行代码定义了部门类Department,第④行代码weak var manager : Employee?声明部门领导的属性,它的类型是Employee可选类型,使用关键字weak声明为弱引用。

第⑤行代码emp!.dept = dept建立强引用关系,第⑥行代码dept!.manager = emp建立弱引用关系。如图17-7所示,其中的关系②是弱引用关系。

{%}

图 17-7 empdept对象之间建立关系

如果我们通过第⑦行代码emp = nil和第⑧行代码dept = nil断开引用关系,如图17-8所示,由于没有指向Employee对象的强引用,所以Employee对象会被释放。①号强引用关系也会被打破,Department对象被释放。

{%}

图 17-8 empdept弱引用断开

17.3.2 无主引用

无主引用与弱引用一样,允许循环引用中的一个对象不采用强引用方式引用另外一个对象,这样就不会引起强引用循环问题。无主引用适用于引用对象永远有值的情况,它总是被定义为非可选类型,使用关键字unowned表示这是一个无主引用。

例如17.2节中介绍的人力资源管理系统,Employeedept属性关联到DepartmentDepartmentmanager(部门领导)属性关联到Employee。如果我们规定的业务需求是一个员工可以没有部分(刚刚入职),即Employeedept(所在部门)属性可以为nil,一个部分不能没有领导,Departmentmanager(部门领导)属性是不能为nil的。如图17-9所示,其中Employeedept声明中有问号“?”,说明是可选类型,也就是可以为nil。而Departmentmanager声明中没有问号“?”,说明是非可选类型,不可以为nil

{%}

图 17-9 EmployeeDepartment关联关系

示例代码如下:

  1. class Employee {
  2. var no : Int
  3. var name : String
  4. var job : String
  5. var salary : Double
  6. var dept : Department?
  7. init(no : Int, name: String, job : String, salary : Double) {
  8. self.no = no
  9. self.name = name
  10. self.job = job
  11. self.salary = salary
  12. println("员工\(name) 已经构造成功。")
  13. }
  14. deinit {
  15. println("员工\(name) 已经析构成功。")
  16. }
  17. }
  18. class Department {
  19. var no : Int = 0
  20. var name : String = ""
  21. unowned var manager : Employee
  22. init(no : Int, name: String, manager : Employee) {
  23. self.no = no
  24. self.name = name
  25. self.manager = manager
  26. println("部门\(name) 已经构造成功。")
  27. }
  28. deinit {
  29. println("部门\(name) 已经析构成功。")
  30. }
  31. }
  32. var emp: Employee?
  33. emp = Employee(no: 7698, name: "Blake", job :"Salesman", salary : 1600)
  34. emp!.dept = Department(no : 30, name: "Sales", manager : emp!)
  35. 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 empdept对象之间建立关系

第⑦行代码通过emp = nil语句断开强引用关系。如图17-11所示,由于没有指向Employee对象的强引用,所以Employee对象会被释放。然后①号强引用关系也被打破,Department对象被释放。

{%}

图 17-11 empdept无主引用断开

另外,上述代码第②行的可选类型可以声明为如下形式:

  1. var dept : Department!

这样当我们引用该对象时候可以采用隐式拆封。隐式拆封表达式是emp!.dept.name,但如果不使用隐式拆封,表达式形式是emp!.dept!.name。使用可选类型隐式拆封可以使代码变得更加简洁,引用它的时候省略了感叹号(!)。