17.4 闭包中的强引用循环
由于闭包本质上也是引用类型,因此也可能在闭包和上下文捕获变量(或常量)之间出现强引用循环问题。
并不是所有的捕获变量(或常量)都会发生强引用循环问题,只有将一个闭包赋值给对象的某个属性,并且这个闭包体使用了该对象,才会产生闭包强引用循环。
17.4.1 一个闭包中的强引用循环示例
我们通过一个示例来了解一下闭包中的强引用循环。示例代码如下:
class Employee { ①
var no : Int = 0
var firstName : String
var lastName : String
var job : String
var salary : Double
init(no : Int, firstName : String, lastName : String, job : String, salary : Double) {
self.no = no
self.firstName = firstName
self.lastName = lastName
self.job = job
self.salary = salary
println("员工\(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 = 0
var firstName : String
var lastName : String
var job : String
var salary : Double
init(no : Int, firstName : String, lastName : String, job : String, salary : Double) {
self.no = no
self.firstName = firstName
self.lastName = lastName
self.job = job
self.salary = salary
println("员工\(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] in
letfn = self.firstName
letln = self.lastName
returnfn + "." + ln
}
采用哪一种引用方式,可以根据用户需求而定,这里不再赘述。此外,我们还可以根据需要为闭包提供参数。