闭包表达式语法
闭包表达式语法有如下的一般形式:
{ (parameters) -> returnType in
statements
}
闭包只有在函数中做参数时,才会区分逃逸闭包和非逃逸闭包
Swift 3.0之后,传递闭包到函数中的时候,系统会默认为非逃逸闭包类型(NoneescapingClosures)@noescaping,逃逸闭包在闭包前要添加@escaping关键字
从生命周期看两者区别
- 非逃逸闭包的生命周期与函数相同
- 把闭包作为参数传递给函数;
- 函数中调用闭包
- 退出函数,结束
- 逃逸闭包的生命周期
- 闭包作为参数传递给函数
- 退出函数
- 闭包被调用,闭包生命周期结束
即逃逸闭包的生命周期长于函数,函数退出的时候,逃逸闭包的引用仍被其他对象持有,不会在函数结束时释放
非逃逸闭包
class HttpTool: NSObject {
func loadData(callBack:(String)->()){ ///(1)
callBack("非逃逸闭包") ///(2)
} ///(3)
}
class ViewController: UIViewController {
var tools: HttpTool = HttpTool()
override func viewDidLoad(){
super.viewDidLoad()
tools.loadData {(data) in
print(data)
}
}
}
代码执行顺序(1),(2),(3)
当传递闭包参数给函数loadData时,要注意viewcontroller中的属性tools,虽然闭包会捕获self,但是由于默认闭包参数时非逃逸类型,这里可以省略self,编译器已经知道这里不会有循环引用的潜在风险。
逃逸闭包
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getData { (data) in
print("闭包结果返回--\(data)--\(Thread.current)")
}
}
func getData(closure:@escaping (Any) -> Void) {
print("函数开始执行--\(Thread.current)")
DispatchQueue.global().async {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
print("执行了闭包---\(Thread.current)")
closure("345")
})
}
print("函数执行结束---\(Thread.current)")
}
}
以上代码最后的执行结果为:
当传递闭包参数给函数getData时,要注意ViewController中的属性tools,这里闭包函数的生命周期在函数结束后结束,tools前面省略的self 就有必要做特殊处理,防止造成死循环。逃逸闭包前面添加@escaping关键字,这里闭包的生命周期不可预知。
为什么要分逃逸闭包和非逃逸闭包
为了管理内存,闭包会强引用它捕获的所有对象,比如你在闭包中访问了当前控制器的属性、函数,编译器会要求你在闭包中显示 self 的引用,这样闭包会持有当前对象,容易导致循环引用。
非逃逸闭包不会产生循环引用,它会在函数作用域内释放,编译器可以保证在函数结束时闭包会释放它捕获的所有对象;使用非逃逸闭包的另一个好处是编译器可以应用更多强有力的性能优化,例如,当明确了一个闭包的生命周期的话,就可以省去一些保留(retain)和释放(release)的调用;此外非逃逸闭包它的上下文的内存可以保存在栈上而不是堆上。
综上所述,如果没有特别需要,开发中使用非逃逸闭包是有利于内存优化的,所以苹果把闭包区分为两种,特殊情况时再使用逃逸闭包。
经常使用逃逸闭包的2个场景:
异步调用: 如果需要调度队列中异步调用闭包,比如网络请求成功的回调和失败的回调,这个队列会持有闭包的引用,至于什么时候调用闭包,或闭包什么时候运行结束都是不确定,上边的例子。
存储: 需要存储闭包作为属性,全局变量或其他类型做稍后使用,例子待补充。