另一个闭合中的闭合是逃逸或非逃逸

Closure inside another closure is escaping or non-escaping

提问人:itsAj 提问时间:8/18/2023 最后编辑:HangarRashitsAj 更新时间:8/19/2023 访问量:49

问:

func perform(_ clsr: () -> Void) {
    let anotherClosure = {
        clsr()
    }
}

上面的代码被编译了,但是当将 更改为 时,编译器给出了错误:anotherClosurevar

转义闭包捕获非转义参数“clsr”

func perform(_ clsr: () -> Void) {
    var anotherClosure = { // error: Escaping closure captures non-escaping parameter 'clsr'
        clsr()
    }
}

func perform(_ clsr: () -> Void) {
    // now it's let still I get the error
    let anotherClosure = {
        clsr()
    }
    
    anotherClosure() // error: Escaping closure captures non-escaping parameter 'clsr'
}

func perform(_ clsr: () -> Void) {
 
    let anotherClosure = {
        clsr()
    }
    
    anotherPerfom(anotherClosure) // Now it's works fine
}

func anotherPerfom(_ clsr: () -> Void){
  clsr()
}

这背后发生了什么?谁能给我解释一下?

快速 关闭

评论

0赞 matt 8/18/2023
没有任何事情“在幕后发生”。它就在你面前。这是存储和立即执行之间的区别。
0赞 JeremyP 8/18/2023
我认为诊断是有问题的。仅仅将闭包的定义从 更改为 不应使其转义。letvar
0赞 matt 8/18/2023
@JeremyP 它不是越野车;它知道的比你多。一个未引用的 let 被简单地优化掉了。就好像方法主体是空的一样。传入的闭包参数永远不会被存储,因为它不必被存储。但有了它,它必须真正存储;这是一个逃避的关闭。var

答:

2赞 matt 8/18/2023 #1

须知事项:

  • 转义意味着函数存储在某个地方以供以后执行。如果要存储函数参数以供以后执行,则必须将其函数类型标记为 。@escaping

  • 此处的闭包是一个匿名函数体。闭包能够捕获在函数外部定义的值。这种捕获是一种存储形式(这是使闭包非常酷和有用的部分原因)。

好的,我们开始吧。


案例 1

func perform(_ clsr: () -> Void) {
    let anotherClosure = {
        clsr()
    }
}

这将初始化一个不可变值 ,而不对它进行任何后续引用。因此,从编译器的角度来看,这几乎就像是没有内容一样。您会收到一条警告,指出您有一个未引用的,但仅此而已;编译器不需要考虑 因为 的内容将被优化。anotherClosureperformletanotherClosureanotherClosure


案例 2

func perform(_ clsr: () -> Void) {
    var anotherClosure = {
        clsr()
    }
}

和以前一样,但现在是一个.这意味着它不会被优化;这次是“真实的”。因此,通过在其中存储一个闭包来初始化,该闭包使用其闭包捕获能力来引用(并因此捕获)参数。letvaranotherClosureanotherClosureclsr

因此,您正在执行错误消息所说的操作:正在使用转义闭包(大括号)进行初始化,该闭包捕获了哪些是非转义参数(因为您没有说)。要解决这个问题,你会说anotherClosureclsr@escaping

func perform(_ clsr: @escaping () -> Void) {

案例 3

func perform(_ clsr: () -> Void) {
    let anotherClosure = {
        clsr()
    }
    anotherClosure()
}

这就像第一个,带有 ,但现在后续的 ,所以不能被优化掉。但这意味着情况与第二个完全相同(具有 );编译器必须考虑如何存储捕获传入参数的闭包(大括号),并且出于完全相同的原因,它得出的结论与上一个示例完全相同。letanotherClosureanotherClosurevaranotherClosureclsr


案例 4

最后但并非最不重要的一点是:

func perform(_ clsr: () -> Void) {
    let anotherClosure = {
        clsr()
    }
    anotherPerform(anotherClosure)
}

func anotherPerform(_ clsr: () -> Void) {
    clsr()
}

这个例子就像第一个例子一样——这是一个可以优化的例子。如果你说我们会回到第二个例子,但你没有。就好像你说过一样letvar

func perform(_ clsr: () -> Void) {
    anotherPerform({ clsr() })
}

func anotherPerform(_ clsr: () -> Void) {
    clsr()
}

这样写,我们可以看到实际上没有发生任何一个值的存储;第一个函数的执行是立即的。因此,不会对任何内容进行转义,并且不必将函数类型声明为 。clsrclsr@escaping