「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」。
本文主要分析逃逸闭包 、非逃逸闭包、自动闭包
逃逸闭包 & 非逃逸闭包
逃逸闭包定义
当闭包作为一个实际参数传递给一个函数时,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当声明一个接受闭包作为形式参数的函数时,可以在形式参数前写@escaping来明确闭包是允许逃逸的
-
如果
用@escaping修饰闭包后,我们必须显示的在闭包中使用self -
swift3.0之后,系统默认闭包参数就是被
@nonescaping,可以通过SIL来验证
- 1、执行时机:在函数体内执行
- 2、闭包生命周期:函数执行完之后,闭包也就消失了
逃逸闭包的两种调用情况
- 1、延迟调用
- 2、作为属性存储,在后面进行调用
1、作为属性
当闭包作为存储属性时,主要有以下几点说明:
- 1、
定义一个闭包属性 - 2、在
方法中对闭包属性进行赋值 - 3、在
合适的时机调用(与业务逻辑相关)
如下所示,当前的complitionHandler作为CJLTeacher的属性,是在方法makeIncrementer调用完成后才会调用,这时,闭包的生命周期要比当前方法的生命周期长
//*********1、闭包作为属性
class CJLTeacher {
//定义一个闭包属性
var complitionHandler: ((Int)->Void)?
//函数参数使用@escaping修饰,表示允许函数返回之后调用
func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){
var runningTotal = 0
runningTotal += amount
//赋值给属性
self.complitionHandler = handler
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("CJLTeacher deinit")
}
}
//使用
var t = CJLTeacher()
t.doSomething()
t.complitionHandler?(10)
<!--打印结果-->
10
2、延迟调用
- 【延迟方法中使用】 1、在
延迟方法中调用逃逸闭包
class CJLTeacher {
//定义一个闭包属性
var complitionHandler: ((Int)->Void)?
//函数参数使用@escaping修饰,表示允许函数返回之后调用
func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){
var runningTotal = 0
runningTotal += amount
//赋值给属性
self.complitionHandler = handler
//延迟调用
DispatchQueue.global().asyncAfter(deadline: .now()+0.1) {
print("逃逸闭包延迟执行")
handler(runningTotal)
}
print("函数执行完了")
}
func doSomething(){
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("CJLTeacher deinit")
}
}
//使用
var t = CJLTeacher()
t.doSomething()
<!--打印结果-->
函数执行完了
逃逸闭包延迟执行
10
当前方法执行的过程中不会等待闭包执行完成后再执行,而是直接返回,所以当前闭包的生命周期要比方法长
逃逸闭包 vs 非逃逸闭包 区别
-
非逃逸闭包:一个接受闭包作为参数的函数,闭包是在这个函数结束前内被调用,即可以理解为
闭包是在函数作用域结束前被调用- 1、
不会产生循环引用,因为闭包的作用域在函数作用域内,在函数执行完成后,就会释放闭包捕获的所有对象 - 2、针对非逃逸闭包,
编译器会做优化:省略内存管理调用 - 3、非逃逸闭包捕获的上下文
保存在栈上,而不是堆上(官方文档说明)。注:针对这点目前没有验证出来,有验证出来的童鞋欢迎留言解惑
- 1、
-
逃逸闭包:一个接受闭包作为参数的函数,逃逸闭包可能会在函数返回之后才被调用,即
闭包逃离了函数的作用域- 1、
可能会产生循环引用,因为逃逸闭包中需要显式的引用self(猜测其原因是为了提醒开发者,这里可能会出现循环引用了),而self可能是持有闭包变量的(与OC中block的的循环引用类似) - 2、一般用于异步函数的返回,例如网络请求
- 1、
-
使用建议:如果没有特别需要,开发中使用
非逃逸闭包是有利于内存优化的,所以苹果把闭包区分为两种,特殊情况时再使用逃逸闭包