Swift可选逃逸闭包

16

输入图像描述

编译器错误Closure use of non-escaping parameter 'completion' may allow it to escape,这是有道理的,因为它会在函数返回后被调用。

func sync(completion:(()->())) {
    self.remoteConfig.fetch(withExpirationDuration: TimeInterval(expirationDuration)) { (status, error) -> Void in
        completion()
    }
}

但是如果我将闭包设置为可选项,那么就不会出现编译器错误,为什么呢?因为即使函数返回后,闭包仍然可以被调用。

func sync(completion:(()->())?) {
    self.remoteConfig.fetch(withExpirationDuration: TimeInterval(expirationDuration)) { (status, error) -> Void in
        completion?()
    }
}

1
可能是重复的问题,参考链接:https://dev59.com/51kS5IYBdhLWcg3wn3--(特别是这个答案:https://dev59.com/51kS5IYBdhLWcg3wn3--#39846519) - Hamish
可能不完全是重复,但感谢@Hamish提供的链接。 - matt
这里有一篇由Ole Begemann撰写的优秀文章,描述了为什么会发生这种情况以及如果您想要可选参数为@noescape,则可以使用一些解决方法。 - Senseful
2个回答

28

将闭包包装在 Optional 中会自动标记它为逃逸闭包。从技术上讲,它已经通过嵌入到枚举(Optional)中“逃逸”了。


1
为什么会这样呢?这背后的想法是什么? - TNguyen
5
有趣。我猜这是因为它作为属性值被分配到了一个包装器中,已经被转义了。 - matt
2
@matt 没错。如果你将一个闭包放在结构体内部,它也会自动变成“逃逸闭包”。 - Rob Napier
@matt 是的,我明白 optional 的工作原理,但这对我来说并不明显。非常有趣! - TNguyen
他们应该考虑使用第一句话作为错误信息。 - somebody4

21

澄清:

为了理解此案例,实现以下代码将会非常有用:

typealias completion = () -> ()

enum CompletionHandler {
    case success
    case failure

    static var handler: completion {
        get { return { } }
        set { }
    }
}

func doSomething(handlerParameter: completion) {
    let chObject = CompletionHandler.handler = handlerParameter
}

乍一看,这段代码似乎是合法的,但实际上并不是!你会得到编译时错误的投诉:

error: assigning non-escaping parameter 'handlerParameter' to an @escaping closure

let chObject = CompletionHandler.handler = handlerParameter

并附带一条注释:

note: parameter 'handlerParameter' is implicitly non-escaping func doSomething(handlerParameter: completion) {

为什么呢?假设代码片段与 @escaping 没有关系...

实际上,从Swift 3发布开始,如果闭包默认声明在枚举、结构体或类中,它就会被“逃逸”。

参考文献,有相关问题汇报:

虽然它们可能与此案例没有百分之百相关性,但受让人的评论清楚地描述了该情况:

第一评论:

这里的实际问题是可选闭包现在隐式为 @escaping。

第二评论:

很遗憾,在Swift 3中是这样的。以下是Swift 3中逃逸的语义:

1)函数参数中的闭包默认为非逃逸

2)所有其他闭包都是逃逸的

因此,所有泛型类型参数中的闭包,如Array和 Optional,都是逃逸的。

显然,Optional 是枚举。

同样,如上所述,相同的行为也适用于类和结构体:

类情况:

typealias completion = () -> ()

class CompletionHandler {
    var handler: () -> ()

    init(handler: () -> ()) {
        self.handler = handler
    }
}

func doSomething(handlerParameter: completion) {
    let chObject = CompletionHandler(handler: handlerParameter)
}

结构体案例:

typealias completion = () -> ()

struct CompletionHandler {
    var handler: completion
}

func doSomething(handlerParameter: completion) {
    let chObject = CompletionHandler(handler: handlerParameter)
}

这两个代码片段将导致相同的输出(编译时错误)。

为了解决这个问题,您需要让函数签名变为:

func doSomething( handlerParameter: @escaping completion)

回到主要问题:

既然您预计必须让 completion:(()->())? 转义,那么如上所述会自动完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接