@noescape在swift3.0中已经被废弃,在swift3.0中@noescape被用作一个默认值。
@escaping属性写在参数类型的前面而不是参数名称的前面。这是swift3里一个新的点。
这里需要先介绍一下escape的概念。当一个闭包当做一个参数传进函数里,这个闭包是在这个函数执行完后执行的,这个时候我们就说这个闭包从函数逃出来了(escape)。这种场景很常见,比如我们进行一个异步的请求,请求时会传入一个handler,比如当请求成功后执行达到回调的目的。
众所周知swift的内存管理是引用计数。闭包里用到的数据都需要捕捉到闭包里,保证闭包执行时这些数据不会被释放还在内存里。Xcode为了让我们意识到闭包里用到的对象其实已经被retain了,就要求我们访问当前属性时显示声明self。这个时候如果新手就很容易犯引用循环的错误。闭包retain了self,self如果又持有retain了闭包。最后就谁都释放不了,内存就泄露了。
swift里针对非escape用@noescape表示。
比如map函数就使用了:
funcmap<T>(@noescape transform: (Self.Generator.Element)throws ->T)rethrows -> [T]
这样标记之后能看到的好处就是这个闭包里如果再使用self的属性不需要加self.了。对于编译器而言,在知道是noescape闭包后可以进行一些内存的优化。
非逃逸闭包(@noescape)
如果这个闭包是在这个函数结束前内被调用,就是非逃逸的即noescape。
逃逸闭包(@escaping)(Escaping Closure)
如果一个闭包被作为一个参数传递给一个函数,并且在函数return之后才被唤起执行,那么这个闭包是逃逸闭包。如果这个闭包是在函数执行完后才被调用,调用的地方超过了这函数的范围,所以叫逃逸闭包@escaping。
举个例子就是我们常用的masonry或者snapkit的添加约束的方法就是非逃逸的。因为这闭包马上就执行了。
public funcsnp_makeConstraints(file: String = #file, line: UInt = #line, @noescape closure: (make: ConstraintMaker) ->Void) -> Void {
ConstraintMaker.makeConstraints(view:self, file: file, line: line, closure: closure)
}
网络请求请求结束后的回调的闭包则是逃逸的,因为发起请求后过了一段时间后这个闭包才执行。比如这个Alamofire里的处理返回json的completionHandler闭包,就是逃逸的。
publicfunc responseJSON(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: Response<AnyObject, NSError> -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer:Request.JSONResponseSerializer(options: options),
completionHandler: completionHandler
)
}
很多人在写闭包参数的时候总是忽略去判断这个闭包是否是逃逸的。这对闭包的内存管理优化不太友好,都被当做了逃逸闭包处理。所以在3中做出了一个对调的改变:所有的闭包都默认为非逃逸闭包,不再需要@noescape;如果是逃逸闭包,就用@escaping表示。比如下面的一段代码,callBack在函数执行完后1秒才执行,所以是逃逸闭包。
func startRequest(callBack: ()->Void ) {
DispatchQueue.global().asyncAfter(deadline:DispatchTime.now() + 1) {
callBack()
}
}
这样就需要显示的声明@escaping才能编译通过。
重点
这里几点关于创建默认不可逃逸闭包的好处:最明显的好处就是编译器优化你的代码的性能和能力。如果编译器知道这个闭包是不可逃逸的,它可以关注内存管理的关键细节。
而且你可以在不可逃逸闭包里放心的使用self关键字,因为这个闭包总是在函数return之前执行,你不需要去使用一个弱引用去引用self.这对你而言是一个非常nice的功能。
参考:http://blog.csdn.net/yydev/article/details/52683329
- @escaping
Swift3的行为更好。因为它默认是安全的:如果一个函数参数可能导致引用循环,那么它需要被显示地标记出来。@escaping标记可以作为一个警告,来提醒使用这个函数的开发者注意引用关系。非逃逸闭包可用被编译器高度优化,快速的执行路径将被作为基准而使用,除非你在有需要的时候显式地使用其他方法。
func doWork(block:()->()) {
print("header")
block()
print("footer")
}
doWork {
print("work")
}
//控制台打印的消息如下:
//header
//work
//footer
对于上述的block调用是同步行为。我们修改一下代码,将block放到一个异步操作中,让它在doWork返回后被调用。这个时候我们就需要用@escaping标记表明这个闭包是会“逃逸”的。
func doWorkAsync(block: @escaping () -> ()) {
DispatchQueue.main.async {
block()
}
}
没有逃逸的闭包的作用域是不会超过函数本身的,所以说我们不需要担心在闭包内持有self。逃逸的闭包就不同了,因为需要确保闭包内的成员依然有效,如果在闭包内引用self以及self的成员的话,就要考虑闭包内持有self的情况了。
class S {
var foo = "foo"
func method1() {
doWork {
print(foo)
}
foo = "bar"
}
func method2() {
doWorkAsync {
print(self.foo)
}
foo = "bar"
}
func method3() {
doWorkAsync {
[weak self] _ in
print(self?.foo)
}
foo = "bar"
}
}
S().method1()// foo
S().method2()// bar
S().method3()// nil
method1不需要考虑self .foo的持有情况,而method2需要考虑,我们让闭包持有了self,打印的值就是foo赋值之后的内容bar,如果我们不希望闭包内持有self的话,可以使用[weak self]的方法来表示. method3就是这样,在闭包执行的时候已经没有了对实例对象的引用,所有说输出是nil。
- weak和 unowned
- 上面我用的是 [weak self],如果用[unowned self]表示的话,method3就需要稍做修改:funcmethod3() {
- doWorkAsync {
- [unowned self] _ in
- print(self.foo)
- }
- foo = "bar"
- }
这两者都是用来防止循环引用的 - unowned有点像oc里面的unsafe_unretained,而weak就是以前的weak。
- 对于这两者的使用,要视情况而定。用unowned的话,即使它原来的引用的内容被释放了,它仍然会保持对被已经释放了的对象的一个引用,它不能是Optional也不能是nil值,这个时候就会出现一个问题,如果你调用这个引用方法或者访问成员属性的话,就会出现崩溃。
- 而weak要稍微友善一点,在引用的内容被释放之后,会自动将weak的成员标记为nil。有人要说,既然这样,那我全部使用weak。但是在可能的情况下,我们还是应该倾向于尽量减少出现Optional的可能性,这样有助于代码的简化。Apple给我们的建议是如果能够确定访问时不会被释放的话,尽量用unowned,如果存在被释放的可能性的话,就用weak。
- S().method1()// foo
- S().method2()// bar
- S().method3()// nil
参考资料:http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground