「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」。
- 本文主要介绍逃逸闭包和自动闭包
1. 逃逸闭包
先看逃逸闭包的定义:当闭包作为一个实际参数
传递给函数的时候,并且是在函数返回之后进行调用,我们就说这个闭包逃逸
了。当我们声明一个接收闭包
为形式参数
的函数
时,可以在形式参数闭包前面写@escaping
,来明确闭包是允许逃逸
的。
因此我们可以说逃逸闭包要满足下面3个条件:
- 作为函数的
参数传递
- 当前闭包在函数内部
异步执行
或被存储
- 函数
结束
,闭包才调用
,生命周期结束
我们看下实际开发中逃逸闭包的2种情况,
1.1 闭包作为属性进行存储
class Person {
var closure:((Int)->Void)?
func makeIncrementer(_ amount: Int,closure:@escaping (Int)->Void){
var runningTool = 10
runningTool += amount
self.closure = closure
}
func test(){
self.makeIncrementer(20) {
print($0)
}
}
}
这里我们把这个闭包作为属性值进行传递
,闭包不加@escaping
修饰的话会报错
这样我们这个逃逸闭包的生命周期就会在这个函数作用域外调用
,它的生命周期是大于
这个函数的生命周期的。
1.2 异步执行
DispatchQueue.global().asyncAfter(deadline: .now()+1) {
closure(runningTool)
}
我们在异步延时调用
这个闭包,这个闭包也是逃逸闭包
。那么闭包和逃逸闭包有什么区别呢,我们看一个非逃逸闭包。
func testNoEscaping(_ f:()->Void){
f()
}
func test()->Int{
var amount = 10
testNoEscaping {
amount += 10
}
return amount
}
print(test())
我们编译sil
文件查看这个闭包
可以发现闭包默认是非逃逸闭包
,其次这里并没有捕获amount到函数中,因为闭包的生命周期和函数是相同
的,函数执行完成后闭包也执行完毕。我们看下ir
代码
test函数并没有
我们之前捕获变量
时候在堆区创建空间的swift_allocObject
方法,说明没有捕获值,因为闭包和函数的生命周期一致
。
我们定义一个闭包变量,进行赋值,这个时候就要提醒我们使用逃逸闭包
,因为逃逸闭包的生命周期明显大于
这个函数,我们编译城ir
代码
通过swift_allocObject
此时就把我们的amount
捕获到了堆区。
1.3 非逃逸闭包的好处
不会产生循环引用
,函数作用域内进行释放。- 编译器有更多的
性能优化
(没有循环引用,就不会retain和release减少消耗) - 非逃逸闭包会把当前的上下文
保存到栈上
,不是堆上。
2. 自动闭包
是一种用来把实际参数传递给函数表达式打包的闭包
,不接受
任何实际参数,当其调用是返回内部表达式的值
。
好处:用普通表达式代替闭包的写法,语法糖
的一种
我们看一个例子
func debugOutPrint(_ condition: Bool , _ message: String){
if condition {
print("lg_debug:\(message)")
}
}
func test()->String{
//耗时操作
return "Application Error Occured"
}
debugOutPrint(true, test())
当我们的判断条件为true的时候才会打印,但是如果我们test中是耗时操作,我希望当前的message
既可以是值String
,也可以是个方法
。我们就可以使用自动闭包进行修饰
这种闭包不接受
任何参数,当它被调用的时候,会返回传入的值。这种便利语法让你在调用的时候能够省略闭包的花括号
。
3. 总结
- 逃逸闭包通常是作为
属性
进行存储或者是异步函数
中执行闭包,通常闭包默认是非逃逸的,逃逸闭包
的生命周期大于函数的生命周期
。 - 自动闭包是一种
语法糖
,这种闭包不接受任何实际参数
,当其调用是,返回内部表达式的值
。