一尘不染

Swift可选的转义闭包

swift

编译器错误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?()
    }
}

阅读 326

收藏
2020-07-07

共1个答案

一尘不染

Clarification:

为了理解这种情况,实现以下代码将很有用:

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

with a note that:

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

这是为什么?假设是代码段已无关的@escaping…

实际上,由于Swift 3已发布,如果默认在enum,struct或class中声明了闭包,则闭包将被“转义” 。

As a reference, there are bugs reported related to this issue:

尽管它们可能与本案并非100%相关,但受让人的评论
清楚地描述了该案:

First
comment
:

The actual issue here is that optional closures are implicitly @escaping
right now.

Second
comment
:

不幸的是,Swift 3就是这种情况。这是
Swift 3中转义的语义:

1)默认情况下,函数参数位置的闭包不转义

2)所有其他关闭都在逃逸

因此,所有通用类型参数闭包(例如Array和Optional)
都在转义。

Obviously,Optional is enum.

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

Class Case:

typealias completion = () -> ()

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

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

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

Struct Case:

typealias completion = () -> ()

struct CompletionHandler {
    var handler: completion
}

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

The two above code snippets would leads to the same output (compile-time
error).

For fixing the case, you would need to let the function signature to be :

func doSomething( handlerParameter: @escaping completion)

Back to the Main Question:

既然你期待,你必须让completion:(()->())?被逃脱,会自动完成-as上面描述。

2020-07-07