当闭包作为参数传递给函数但在函数返回后才调用,该闭包被称为转义闭包。此时可以在参数类型之前添加@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
的可变引用的规则。