17.4 闭包中的强引用循环
由于闭包本质上也是引用类型,因此也可能在闭包和上下文捕获变量(或常量)之间出现强引用循环问题。
并不是所有的捕获变量(或常量)都会发生强引用循环问题,只有将一个闭包赋值给对象的某个属性,并且这个闭包体使用了该对象,才会产生闭包强引用循环。
17.4.1 一个闭包中的强引用循环示例
我们通过一个示例来了解一下闭包中的强引用循环。示例代码如下:
class Employee { ①var no : Int = 0var firstName : Stringvar lastName : Stringvar job : Stringvar salary : Doubleinit(no : Int, firstName : String, lastName : String, job : String, salary : Double) {self.no = noself.firstName = firstNameself.lastName = lastNameself.job = jobself.salary = salaryprintln("员工\(firstName) 已经构造成功。")}deinit {println("员工\(firstName) 已经析构成功。")}lazy var fullName : ()-> String = { ②return self.firstName + "." + self.lastName ③}}var emp : Employee? = Employee(no: 7698, firstName: "Tony", lastName : "Guan",job :"Salesman", salary : 1600)println(emp!.fullName()) ④emp = nil ⑤
上述代码第①行定义了类Employee,第②行代码定义了计算属性fullName,这个属性值的计算是通过一个闭包实现的,闭包的返回值类型是()-> String。属性fullName被声明为lazy,说明该属性是延迟加载的,由于在第③行代码中捕获了self,self能够在闭包体中使用,那么属性必须声明为lazy,即所有属性初始化完成后,self表示的对象才能被创建。
第④行代码emp!.fullName()调用闭包返回fullName属性值。代码第⑤行emp = nil断开强引用,释放对象。程序输出的结果是:
员工Tony 已经构造成功。Tony.Guan
从结果可见,析构器并没有被调用,也就是说对象没有被释放。导致这个问题的原因是闭包与捕获对象之间发生了强引用循环。
17.4.2 解决闭包强引用循环
解决闭包强引用循环问题有两种方法:弱引用和无主引用。到底应该采用弱引用还是无主引用,与两个对象之间的选择条件是一样的。如果闭包和捕获的对象总是互相引用并且总是同时销毁,则将闭包内的捕获声明为无主引用。当捕获的对象有时可能为nil时,则将闭包内的捕获声明为弱引用。如果捕获的对象绝对不会为nil,那么应该采用无主引用。
Swift在闭包中定义了捕获列表来解决强引用循环问题,基本语法如下:
lazy var 闭包: <闭包参数列表> -><返回值类型> = { ①[unowned 捕获对象] <闭包参数列表> -><返回值类型> in ②或 [weak 捕获对象] <闭包参数列表> -><返回值类型> in ③//闭包体}
或
lazy var 闭包: () -> <返回值类型> = { ④[unowned 捕获对象] in ⑤或 [weak 捕获对象] in ⑥//闭包体}
上述语法格式可以定义两种闭包捕获列表,其中第①行代码的语法格式是最为普通的格式,其中<闭包参数列表> -><返回值类型>与第②行和第③行的<闭包参数列表> -><返回值类型>要对应上。示例如下:
lazy var fullName : (String, String) -> String = {[weakself] (firstName : String, lastName : String) -> String in//闭包体}
第④行的语法格式是无参数情况下的捕获列表,可以省略参数类别,只保留in,Swift编译器会通过上下文推断出参数列表和返回值类型。示例如下:
lazy var fullName : () -> String = {[unownedself] in//闭包体}
下面看示例代码:
class Employee {var no : Int = 0var firstName : Stringvar lastName : Stringvar job : Stringvar salary : Doubleinit(no : Int, firstName : String, lastName : String, job : String, salary : Double) {self.no = noself.firstName = firstNameself.lastName = lastNameself.job = jobself.salary = salaryprintln("员工\(firstName) 已经构造成功。")}deinit {println("员工\(firstName) 已经析构成功。")}lazy var fullName : ()-> String = {[weak self] ()-> String in ①letfn = self!.firstName ②letln = self!.lastName ③returnfn + "." + ln}}var emp : Employee? = Employee(no: 7698, firstName: "Tony", lastName : "Guan",job :"Salesman", salary : 1600)println(emp!.fullName())emp = nil
我们将第①行代码修改为[weak self] ()-> String in,该捕获列表是弱引用,捕获对象是self。由于是弱引用,在引用self的时候,代码第②行和第③行需要在后面加感叹号“!”,表明是强制拆封。
程序输出的结果是:
员工Tony 已经构造成功。Tony.Guan员工Tony 已经析构成功。
从结果可见析构器被调用了。
我们可以将fullName属性定义为无主引用的捕获列表形式,代码如下:
lazy var fullName : ()-> String = {[unownedself] inletfn = self.firstNameletln = self.lastNamereturnfn + "." + ln}
采用哪一种引用方式,可以根据用户需求而定,这里不再赘述。此外,我们还可以根据需要为闭包提供参数。
