Swift逃逸闭包@escaping
Swift逃逸闭包@escaping

Swift逃逸闭包@escaping

在 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异步

2、Swift ARC自动引用计数

3、Swift闭包在类中的引用问题

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注