如果大家有去看swift的API的话,会发现有些API使用了“@noescape”符号。比如,SequenceType协议的map
public func map<T>(@noescape transform: (Self.Generator.Element)throws -> T) rethrows -> [T]
大家对map应该不陌生。它是用来对结构中的数据做映射。数组中,map的实现大致是这样:对数组的每个元素做映射,映射成其他的值然后生成一个新的数组返回给调用者
extension Array {
func map<U>(transform: Element->U) -> [U] {
var result: [U] = []
result.reserveCapacity(self.count)
for x in self {
result.append(transform(x))
}
return result
}
}
大家应该也知道,闭包使用不当的时候,会引起引用循环。为了防止出现引用循环我们会在闭包中使用捕获列表。但是,我们在使用map的时候,即使调用了self也不会造成引用循环。我们几乎没有在map的闭包参数上使用捕获列表来防止引用循环(好吧,即使使用了捕获列表,其实也是没什么影响。就是多写了些冗余的代码而已)。
请看如下栗子:
【栗子1】
图1-1
从图1-1代码的运行结果中,我们可以看到,原来创建的ClassA实例被正常销毁了。代码并没有造成引用循环。
为什么会这样呢?这是因为在map中,transform闭包仅仅是在调用map的时候生成的。它的生命周期就在map函数里,当map执行完,tranform闭包的生命周期就结束了。即使我们在map中使用了self,也不会造成引用循环。因为在这样的情况下,虽然闭包持有实例对象,但是闭包只被map参数持有。 如图1-2:
图1-2
noescape是非逃逸的意思。@noescape关键字代码中扮演了一个标注的作用:来说明一个闭包参数,该闭包参数与此API是同步的,它只在此API中被调用。只要该API运行结束,该闭包的生命周期就结束。也就是说,该闭包逃不出该API的手掌心。哈哈哈哈!它对编译器和API调用者来说:编译器会对代码做一些优化,而API调用者则可以放心大胆的使用该API,不用因为担心造成引用循环而去使用捕获列表。同时在其中调用实例变量或实例方法的时候可以不使用"self."
氮素!如何使用这个@noescape标注,这是需要正确的姿势的!
上面的论述,只有在闭包是临时创建,即没有被API外部的任何其他属性或全局变量持有的前提下才成立!!
【API调用者的正确姿势】
让我们来看看,当闭包参数被其他属性持有的情况下,使用map是什么结果。请看下面的代码:
【栗子2】
图 2-1
可以看出,控制台并没有打印"ClassA 实例被销毁",也就是说,ClassA实例的deinit方法没有被调用。原先的ClassA实例没有被销毁。这段代码存在引用循环。(具体分析请见我上一节的博客)
这是因为,传入map的闭包,被map外部的变量所持有。transform跟map再也不同步了。也不一定只有在map内部才会调用。如果我们高兴,可以在调用完a.handleDataArr()后接着调用a.transform。这个时候,该闭包是逃逸类型的闭包。它可以逃出map的手掌心。在这样的情况下,我们就必须要使用捕获列表,才能避免造成引用循环!
【总结】: 我们在调用被@noescape标注的API的时候,如果我们传入的闭包被其他任何属性或者全局变量持有,我们一定要在闭包中使用捕获列表以避免产生引用循环;如果我们传入的只是临时创建的闭包(即没有被其他任何属性或全局变量所持有),则可以随意使用该API,不必使用捕获列表,调用属性或方法时也不必使用“self.”。
【API创建者的正确姿势】
由于编译器会优化带有@noescape的代码。所以,当我们自己写API的时候,如果有闭包参数,应该尽量写上@noescape。 如果你试图在该API中使用API外部变量来指向该闭包,编译器会报错: "cannot assign value of type '@noescape Int -> Int' to type '(Int -> Int)?"
【栗子3】给自己的代码添加@noescape
图 3-1
最后,你get到@noescape使用的正确姿势了吗?^_^