在Swift中有两种闭包,逃逸闭包(@escaping)和非逃逸闭包(@nonescaping)。从Swift 3开始,函数中传递的闭包默认为@nonescaping。
怎么让一个闭包逃逸?
简而言之,逃逸闭包就是函数运行完后,闭包还没有执行,这时称闭包逃逸了。
var bar_closure : ()->Void = {}
func bar(closure: @escaping ()->Void) {
bar_closure = closure
}
另一个稍微复杂一点的例子, 来自http://stackoverflow.com/questions/39504180/escaping-closures-in-swift
class A {
var closure: (() -> Void)?
func someMethod(closure: () -> Void) {
self.closure = closure
}
}
class B {
var number = 0
var a: A = A()
func anotherMethod() {
a.someMethod { self.number = 10 }
}
}
结论:
可见让一个闭包逃逸最简单的方法就是在函数外定义一个变量来保存该闭包。
小心“强引用循环” (retain cycles)!
观察上面的例子,可能发现它其实存在内存泄漏,因为有强引用循环。给两个类添加deinit, 发现A,B实例均没有被释放。
class A {
var closure: (() -> Void)?
func someMethod(@escaping closure: () -> Void) {
self.closure = closure
}
deinit {
print("A deinit")
}
}
class B {
var number = 0
var a: A = A()
func anotherMethod() {
a.someMethod { self.number = 10 }
}
deinit {
print("B deinit")
}
}
let b = B()
b.anotherMethod()
引用循环图
解决方法
closure 中使用弱引用捕获self的时候,
func anotherMethod() {
a.someMethod { [weak self] in
self?.number = 10
}
}
这样当B被释放的时候,其子层级A,A.closure均会被释放。
如果A.someMethod是@nonescaping闭包,会有引用循环么?
func someMethod(closure: () -> Void) {
closure()
}
答案是不会的,因为此时的引用关系如下图, A.someMethod运行结束后,closure即被释放。
参考文章:
https://krakendev.io/blog/weak-and-unowned-references-in-swift