Swfit进阶-16-Swift逃逸闭包和自动闭包

3,014 阅读3分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」。

  • 本文主要介绍逃逸闭包和自动闭包

1. 逃逸闭包

先看逃逸闭包的定义:当闭包作为一个实际参数传递给函数的时候,并且是在函数返回之后进行调用,我们就说这个闭包逃逸了。当我们声明一个接收闭包形式参数函数时,可以在形式参数闭包前面写@escaping ,来明确闭包是允许逃逸的。
因此我们可以说逃逸闭包要满足下面3个条件:

  1. 作为函数的参数传递
  2. 当前闭包在函数内部异步执行或被存储
  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 修饰的话会报错

image.png

这样我们这个逃逸闭包的生命周期就会在这个函数作用域外调用,它的生命周期是大于这个函数的生命周期的。

image.png

1.2 异步执行

DispatchQueue.global().asyncAfter(deadline: .now()+1) {

          
            closure(runningTool)

    }

image.png

我们在异步延时调用这个闭包,这个闭包也是逃逸闭包。那么闭包和逃逸闭包有什么区别呢,我们看一个非逃逸闭包。


func testNoEscaping(_ f:()->Void){

    f()

}

func test()->Int{

    

    var amount = 10

    testNoEscaping {

        amount += 10

    }

    return amount

}

print(test())

我们编译sil文件查看这个闭包

image.png

可以发现闭包默认是非逃逸闭包,其次这里并没有捕获amount到函数中,因为闭包的生命周期和函数是相同的,函数执行完成后闭包也执行完毕。我们看下ir代码

image.png

test函数并没有我们之前捕获变量时候在堆区创建空间的swift_allocObject方法,说明没有捕获值,因为闭包和函数的生命周期一致

image.png

我们定义一个闭包变量,进行赋值,这个时候就要提醒我们使用逃逸闭包,因为逃逸闭包的生命周期明显大于这个函数,我们编译城ir代码

image.png

通过swift_allocObject此时就把我们的amount捕获到了堆区。

1.3 非逃逸闭包的好处

  1. 不会产生循环引用,函数作用域内进行释放。
  2. 编译器有更多的性能优化(没有循环引用,就不会retain和release减少消耗)
  3. 非逃逸闭包会把当前的上下文保存到栈上,不是堆上。

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,也可以是个方法。我们就可以使用自动闭包进行修饰

image.png

这种闭包不接受任何参数,当它被调用的时候,会返回传入的值。这种便利语法让你在调用的时候能够省略闭包的花括号

3. 总结

  1. 逃逸闭包通常是作为属性进行存储或者是异步函数中执行闭包,通常闭包默认是非逃逸的,逃逸闭包生命周期大于函数的生命周期
  2. 自动闭包是一种语法糖,这种闭包不接受任何实际参数,当其调用是,返回内部表达式的值