在 Swift 中,如果闭包在函数执行后仍然可能被调用,需要使用 @escaping 标记闭包参数。
换句话说,带有 @escaping 标记的闭包会在函数作用域之外执行,比如在异步操作、回调中使用的闭包通常是 @escaping 的。
默认行为
在Swift中,闭包参数默认是非逃逸的(@nonescaping),这意味着闭包只能在函数体内调用,不能超出函数的生命周期,也不能在函数结束后被异步调用或保存在外部变量中。
例如,闭包的默认行为:
func performTask(closure: () -> Void) {
// 只能在函数内部调用
closure()
print("函数结束")
}
逃逸闭包
如果函数返回后,延迟调用闭包(例如异步网络请求的回调),需要将闭包声明为 @escaping,否则编译器会报错。
使用逃逸闭包的场景:
1、被异步执行:
func getName(completion: @escaping (String) -> Void) {
DispatchQueue.main.async {
completion("fangjunyu")
}
}
2、被变量保存:
var savedClosure: ((String) -> Void)?
func getName(completion: @escaping (String) -> Void) {
savedClosure = completion
}
如果闭包被赋值到外部属性,属于逃逸闭包。
如果需要调用保存的闭包变量:
savedClosure?() // 可选链调用
或者:
if let closure = savedClosure {
closure()
}
注意事项
1、逃逸闭包使用 self.
当逃逸闭包访问实例成员时,必须显式写 self.
如果不添加,则会报错:
Reference to property 'pastePublisher' in closure requires explicit use of 'self' to make capture semantics explicit
逃逸闭包中,Swift 不允许隐式捕获实例。
在非逃逸闭包中,不需要显式写 self.,因为闭包生命周期很短,和当前函数强绑定,不存在所有权歧义。
但是在逃逸闭包中,Swift要求显式 self. 。Swift要求开发者知道闭包要捕获self。
如果不使用 [weak self],那么就是标准的强引用循环,deinit永远不会被调用。
引用知识点,可以阅读《Swift ARC自动引用计数》
2、普通闭包也可以添加 @escaping
对于普通(非逃逸)的闭包,也可以使用 @escaping 进行标注,@escaping的目的在于告诉编译器,这个闭包可能会逃出函数作用于被保存或异步调用。
闭包逃逸的判定为,当函数参数返回后闭包是否还会被调用,如果被调用,就是逃逸闭包,需要使用 @escaping 进行标注。
private var handlers: [() -> Void] = []
func addHandler(_ handler: @escaping() -> Void) -> Void{
handlers.append(handler) // 函数参数 handler 被保存,属于逃逸闭包
}
而返回闭包如果被变量保存,则不属于“逃逸闭包”的范畴,因为它不是函数参数。
func addHandler() -> () -> Void{
let name = "fang"
return { // 返回一个闭包
print("name:\(name)")
}
}
a = eve.addHandler() // a 持有返回的闭包
// 这种不属于逃逸闭包
// 只有函数参数在方法结束后被保存,才属于逃逸闭包
总结
@escaping表示闭包逃逸出当前函数,函数返回值后仍然可能会被执行,适用于需要延迟执行、异步操作等情况。同时确保闭包生命周期延长,运行在函数返回后调用它,从而避免函数返回后闭包被释放
相关文章
1、Swift异步
