对 Swift 中 @escaping 关键字的理解

4,813 阅读2分钟

在我们的开发过程中,时常会使用到闭包,有的会用 @escaping 关键字修饰,有的则不会。今天,通过这篇文章希望能让大家对何时使用 @escaping,以及为什么使用有一个清晰的认知。

什么时候使用

闭包的生命周期超过当前函数的生命周期时,我们需要使用该关键字标识。

异步执行的闭包

比如下面的代码:

func doAsySomething(completion: @escaping () -> ()) {
    print("begin - doAsySomething")
    DispatchQueue.main.async {
        completion()
    }
    print("end - doAsySomething")
}

doAsySomething {
    print("doAsySomething")
}
// 打印结果:
begin - doAsySomething
end - doAsySomething
doAsySomething

通过上述的打印结果可以发现,虽然 doAsySomething 函数已经执行完,但是 completion 还未执行完,所以它的声明周期已经超过了 doAsySomething 函数的生命周期。因此,这里需要使用 @escaping 关键字标识。

如果不适用 @escaping 标识的话,编译器也会报错。

无法得知闭包何时执行

因为 completion 放在了数组中,而我们无法得知它什么时候会执行,所以也需要使用 @escaping 修饰。

var completions: [()->()]!

func doSomething(completion: @escaping () -> ()) {
    completions.append(completion)
}

无须使用 @escaping

如果闭包的声明周期未超过当前函数的声明周期,则无须使用 @escaping。比如下面的代码:

func doSomething(completion: () -> ()) {
    print("begin")
    completion()
    print("end")
}

doSomething {
    print("do something")
}

// 打印结果:
begin
do something
end

通过打印结果可以得知,闭包的声明周期是在 doSomething 函数之内的。所以不需要使用 @escaping 标识。

为什么使用

上面讲了 @escaping 的使用场景,这里讲一下为什么要使用 @escaping 关键字。有 OC 经验的同学都知道,在实现 block 的时候,需要时刻警惕循环引用的存在。而在 Swift 中,@escaping 正是为了避免大家的代码造成循环引用的问题而存在的。

在 @escaping 标识的闭包中,会强制你显式的使用 self,提醒你此处代码可能会造成循环引用,需要你小心编写此处代码,从而避免代码的出错率。

所以,为什么要使用 @escaping 呢?因为这样可以提醒大家避免代码出错的概率,从而提高代码的质量。

总结

  • 同步执行的闭包无须 @escaping 修饰;异步执行的闭包必须使用 @escaping 修饰。
  • 无法得知闭包何时执行,也需要使用 @escaping 修饰。
  • 使用 @escaping 修饰的闭包,编译器会强制你显式的使用 self,从而避免代码出错的概率,从而提高代码质量。