Swift 之 非逃逸闭包与逃逸闭包

注:在网上看到一篇讨论Swift中非逃逸闭包与逃逸闭包的英文文章 原文链接传送门,觉得有点意思,特整理成中文分享给大家。

=======开始======

众所周知,函数和闭包是Swift语言中的一等公民,在程序中可以把它作为值存储起来,也作为参数传递给函数。

在Swift 3.x 中,传递闭包到函数中的时候,系统会默认为非逃逸闭包类型 (Nonescaping Closures),有非逃逸闭包类型必然就有逃逸闭包(Escaping Closures), 这里简单聊聊两者的关系。

非逃逸闭包

非逃逸闭包的生命周期比较简单:

  1. 把闭包作为参数传递给函数。
  2. 函数中运行该闭包。
  3. 退出函数。
Swift 之 非逃逸闭包与逃逸闭包_第1张图片
非逃逸闭包

显而易见是非逃逸闭包被限制在函数内,当函数退出的时候,该闭包引用计数不会增加,也就是说其引用计数在进入函数和退出函数时保持不变。

逃逸闭包

逃逸闭包恰恰与非逃逸闭包相反,其生命周期长于相关函数,当函数退出的时候,逃逸闭包的引用仍然被其他对象持有,不会在相关函数结束后释放。

Swift 之 非逃逸闭包与逃逸闭包_第2张图片
逃逸闭包

如上图所示,当函数结束的时候,闭包依然会在外面的世界里逍遥快活,对于内存管理来说这可不是好现象。

样例分析

Swift 3.x中, 闭包参数默认是非逃逸类型,如果需要其逃逸类型的闭包,记得使用关键字 @escaping

而对于非逃逸型闭包,由于其生命周期确定短于相关函数,编译器可以据此做性能优化。

class ClassA {
  // 接受非逃逸闭包作为参数
  func someMethod(closure: () -> Void) {
    // 想干什么?
  }
}

class ClassB {
  let classA = ClassA()
  var someProperty = "Hello"

  func testClosure() {
    classA.someMethod {
      // self 被捕获
      someProperty = "闭包内..."
    }
  }
}

当传递闭包参数给函数someMethod时,要注意ClassB中的属性someProperty,虽然闭包会捕获self,但是由于默认闭包参数是非逃逸型,这里可以省略 self, 反正编译器已经知道这里不会有循环引用的潜在风险。

但是如果someMethod换个写法:

func someMethod(closure: @escaping () -> Void) {
  // 特别行动
}

由于添加了关键词@escaping,这里闭包函数的生命周期不可预知,上面省略的self 就有必要明确地添加来提醒self已经被捕捉,循环引用的风险就在眼前。

注意

要谨慎使用@escaping(逃逸闭包),除非明确知道要使用它做什么。

下面是使用逃逸闭包的2个场景:

  1. 异步调用: 如果需要调度队列中异步调用闭包, 这个队列会持有闭包的引用,至于什么时候调用闭包,或闭包什么时候运行结束都是不可预知的。
  2. 存储: 需要存储闭包作为属性,全局变量或其他类型做稍后使用。

欢迎补充更多使用场景。

推荐阅读
Swift高手进阶 - 11个技巧
iOS开发者注意, ATS 出没!

更多

获取更多内容请关注微信公众号豆志昂扬:

  • 直接添加公众号豆志昂扬
  • 微信扫描下图二维码;
Swift 之 非逃逸闭包与逃逸闭包_第3张图片

你可能感兴趣的:(Swift 之 非逃逸闭包与逃逸闭包)