函数中的@Escaping参数

闭包作为函数参数

Posted by Joker Hook on May 14, 2022

当闭包作为参数传递给函数但在函数返回后才调用,该闭包被称为转义闭包。此时可以在参数类型之前添加@escaping关键字,以指示允许闭包转义。

闭包可以转义的一种方法是存储在函数之外定义的变量中。例如,许多启动异步操作的函数将闭包参数作为完成处理程序。该函数在开始操作后返回,但在操作完成之前不会调用闭包。例如:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:)函数以闭包为参数,并将其添加到函数之外声明的数组中。如果您没有用@escaping标记此函数的参数,您将收到编译时错误。

通常,闭包通过在闭包正文中使用变量来隐式捕获变量,但在添加转义字符@escaping这种情况下,需要显式捕获变量。如果您想捕获self,请在使用它时显式写入self,或将self包含在闭包的捕获列表中。写self明确可以让您表达自己的意图,并提醒您确认没有参考周期。例如,在下面的代码中,传递给someFunctionWithEscapingClosure(_:)的闭包显式引用自显式,即添加了self关键字来表示获取变量x。相比之下,传递给someFunctionWithNonescapingClosure(_:)的闭包是一个不可转义闭包,这意味着它可以隐式引用self,即无需添加self关键字来表示获取变量x

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

以下是doSomething()的另一个版本,通过将其包含在闭包的捕获列表中来捕获self,然后隐含地引用self

class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

如果self是结构或枚举的实例,您可以始终隐式引用self。然而,当self是结构或枚举的实例时,转义闭包无法捕获对self的可变引用。结构和枚举不允许共享可变性。

struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}

上面示例中对someFunctionWithEscapingClosure函数的调用是一个错误,因为它位于突变方法中,因此self是可变的。这违反了转义闭包不能捕获结构对self的可变引用的规则。