Swift可选的转义闭包参数


162

鉴于:

typealias Action = () -> ()

var action: Action = { }

func doStuff(stuff: String, completion: @escaping Action) {
    print(stuff)
    action = completion
    completion()
}

func doStuffAgain() {
    print("again")
    action()
}

doStuff(stuff: "do stuff") { 
    print("swift 3!")
}

doStuffAgain()

有什么方法可以使completion参数(和action)的类型Action?也保持不变@escaping

更改类型会出现以下错误:

@escaping属性仅适用于函数类型

删除该@escaping属性后,代码将编译并运行,但是由于completion闭包使函数的作用范围变大,因此似乎并不正确。


21
“删除@escaping属性,代码将编译并运行”-这是因为,如SR-2444中所述Action?默认情况下是转义。因此,@escaping在使用可选闭包时删除即可满足您的需求。
罗布(Rob)2016年


类型别名关闭正在逃逸
Masih

这是Ole Begemann精彩文章,描述了为什么发生这种情况,以及如果您希望将可选参数设置为@noescape的一些解决方法。
有意义,

Answers:


122

有一个SR-2552报告@escaping无法识别功能类型别名。这就是错误的原因@escaping attribute only applies to function types。您可以通过扩展函数签名中的函数类型来解决:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: (@escaping ()->())?) {
    print(stuff)
    action = completion
    completion?()
}

func doStuffAgain() {
    print("again")
    action?()
}

doStuff(stuff: "do stuff") {
    print("swift 3!")
}

doStuffAgain()

编辑1

我实际上是在xcode 8 beta版本下,但尚未解决bug SR-2552。修复了该错误,并引入了一个仍在打开的新错误(您面临的错误)。参见SR-2444

解决方法@迈克尔Ilseman指出作为临时解决方案是去除@escaping从可选功能类型的属性,其保持功能逸出

func doStuff(stuff: String, completion: Action?) {...}

编辑2::

SR-2444已经被关闭,说明明确,在参数位置关闭逃逸,需要他们被打上@escaping让他们逃脱,但可选参数隐含逃避,因为((Int)->())?是同义词Optional<(Int)->()>,可选的闭包逃跑。


5
现在开始@escaping may only be applied to parameters of function type func doStuff(stuff: String, completion: (@escaping ()->())?) {
Lescai Ionel 16/09/21

1
a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping. 您可以进一步解释吗?swift 3中的默认语义是不可转义。尽管它编译时没有@转义,但恐怕会被视为非转义而引起问题。那不是真的吗
Pat Niemeyer 2016年

49
进一步阅读后,我看到SR-2444表示所有可选的闭包都被视为转义,这是一个补充性的错误:)我将假定,在修复后,编译器会警告我们进行更改。
Pat Niemeyer's

也许有点偏离主题;但这是如何工作的@autoclosure?一个人在那里得到同样的错误……
Gee.E

它不使用@转义__ func doStuff(东西:字符串,完成:(()->())?){
ФеннурМезитов18Mar

226

来自:swift-users邮件列表

基本上,@ escaping仅对函数参数位置中的闭包有效。默认情况下,noescape规则仅适用于函数参数位置处的这些闭包,否则将转义。聚集,例如具有关联值的枚举(例如Optional),元组,结构等,如果它们具有闭包,请遵循不在函数参数位置(即转义)的闭包的默认规则。

因此默认情况下,可选的函数参数为@escapeing。
@noeascape默认仅适用于功能参数。


7
我认为这为该主题添加了最重要的信息,应该接受答案。
Damian Dudycz

合理的答案。这可能会改变吗?
GoldenJoe

2
这是有道理的,因为从技术上讲,您的说法(()->Void)?与您所说的相同,Optional<()->Void>并且为了Optional保持所有权,它只需要接受@escaping功能即可。我将把它作为公认的答案。谢谢。
Dean Kelly,

22

我遇到了类似的问题,因为混合@escaping和非@escaping混淆非常混乱,尤其是在需要传递闭包的情况下。

我最终通过分配了一个无操作默认值给闭包参数= { _ in },我认为这更有意义:

func doStuff(stuff: String = "do stuff",
        completion: @escaping (_ some: String) -> Void = { _ in }) {
     completion(stuff)
}

doStuff(stuff: "bla") {
    stuff in
    print(stuff)
}

doStuff() {
    stuff in
    print(stuff)
}

这是干净漂亮的实现。
Fred Faust

2
如果我要检查此块是否为空/空怎么办?
Vyachaslav Gerchicov's

2
Bummer,这不适用于协议方法。“协议方法中不允许使用默认参数”(Xcode 8.3.2)。
Mike Taverne

17

我以这种方式在Swift 3中工作而没有任何警告:

func doStuff(stuff: String, completion: (()->())? ) {
    print(stuff)
    action = completion
    completion?()
}

4

在示例中要理解的重要一点是,如果更改ActionAction?闭包,则在逃避。因此,让我们执行您的建议:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: Action?) {
    print(stuff)
    action = completion
    completion?()
}

好吧,现在我们打电话给doStuff

class ViewController: UIViewController {
    var prop = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        doStuff(stuff: "do stuff") {
            print("swift 3!")
            print(prop) // error: Reference to property 'prop' in closure 
                        // requires explicit 'self.' to make capture semantics explicit
        }
    }
}

好吧,该要求仅在转义闭包时出现。所以关闭是逃避。这就是为什么您不标记为逃避-它已经在逃避。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.