循环强引用还会发生在当你将一个闭包赋值给类实例的某个实例,并且这个闭包体中又实用了这个类实例。这个闭包体重可能访问了实例的某个属性,例如self.**,或者闭包中调用了实例的某个方法,例如self.**,这两种情况都导致闭包“捕获”self,从而产生了循环强引用。
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把闭包赋值给某个属性时,你也把一个引用赋值给了这个闭包。实质上,这跟之前的问题一样,两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。
Swift提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表。
在定义闭包时,同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主易用,而不是强引用。应该根据代码关系来决定使用弱引用还是无主引用。
----------定义捕获列表
捕获列表中的每一项都由一对元素组成,一个元素是weak或unowned关键字,另一个元素是类实例的引用(如self)或初始化过的变量(如delegate = self.delegate!)。这些项在方括号中用逗号分开。
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
如果闭包没有指明参数列表或者返回类型,即他们会通过上下文来判断,那么可以把捕获列表和关键字in放在闭包最开始的地方。
----------弱引用和无主引用
在闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查他们是否存在。
注意:如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。
class HTMLElement { let name:String let text:String? lazy var asHTML:Void -> String = { [unowned self] in //表示“用无主引用而不是强引用来捕获 self ” if let text = self.text{ return "<(self.name)> (text)<(self.name)>" }else{ return "<(self.name)>" } } init(name:String, text:String? = nil){ self.name = name self.text = text } deinit{ print("(name) is being deinitialized") } } var paragraph:HTMLElement? = HTMLElement(name: "p", text: "hello world") print(paragraph!.asHTML())
打印:
<p> hello world<p>
这个闭包这个地方,没有看懂。