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


タイプエイリアスクロージャがエスケープしている
Masih

以下は、Ole Begemannによる優れた記事で、これが発生している理由と、オプションのパラメーターを@noescapeにしたい場合の回避策について説明しています。
18年

回答:


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::

私は実際にはバグSR-2552がまだ解決されていないxcode 8ベータ版の下にいました。そのバグを修正し、まだ開いている新しいバグ(直面しているバグ)を導入しました。SR-2444を参照してください。

一時的な解決策として指摘されている回避策@Michael 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

1
a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping. これをさらに説明できますか?Swift 3のデフォルトのセマンティクスはエスケープしていません。@エスケープせずにコンパイルされますが、エスケープしないものとして扱われることで問題が発生するのではないかと心配しています。それは本当ではないですか?
Pat Niemeyer、

49
さらに読むと、SR-2444はすべてのオプションのクロージャがエスケープとして扱われることを示していることがわかります。これは補足的なバグです:)修正されると、コンパイルによって変更が行われると警告されます。
Pat Niemeyer

たぶん少し話題から外れています。しかし、これはどのように機能し@autoclosureますか?そこで同じエラーが発生します...
Gee.E

@エスケープなしで機能しています__ func doStuff(stuff:String、completion:(()->())?){
ФеннурМезитовMar

226

from:swift-usersメーリングリスト

基本的に、@ escapingは関数パラメーター位置のクロージャーでのみ有効です。noescape-by-defaultルールは、関数パラメーターの位置にあるこれらのクロージャーにのみ適用され、それ以外の場合はエスケープします。関連付けられた値を持つ列挙型(オプションなど)、タプル、構造体などの集合体、クロージャがある場合、関数パラメータの位置にない、つまりエスケープしているクロージャのデフォルトのルールに従います。

したがって、オプションの関数パラメーターはデフォルトで@escapingです。
@noeascapeは、デフォルトでは関数パラメーターにのみ適用されます。


7
これは主題に最も重要な情報を追加すると思います、それは受け入れられるべき答えです。
ダミアンDudycz

推論された答え。これが変わる可能性はどのくらいありますか?
GoldenJoe 2017年

2
これは、技術的に言っているの(()->Void)?はあなたが持っているOptional<()->Void>と言っていることと同じであり、Optionalが所有権を維持するためには、@escaping機能を受け入れるだけでよいためです。これが受け入れられるべき答えであることを私は三番目に申し上げます。ありがとうございました。
ディーンケリー

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)
}

これは、実装がクリーンできれいです。
フレッドファウスト

2
このブロックがnil / emptyかどうかを確認するにはどうすればよいですか?
Vyachaslav Gerchicov

2
残念ながら、これはプロトコルメソッドでは機能しません。「プロトコルメソッドでは既定の引数は許可されていません」(Xcode 8.3.2)。
Mike Taverne

17

この方法で警告なしにSwift 3で動作させました:

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

4

この例で理解しておくべき重要なことは、クロージャに変更ActionするエスケープするAction?ということです。だから、あなたが提案したことをしましょう:

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
        }
    }
}

まあ、その要件は、クロージャをエスケープする場合にのみ発生します。したがって、クロージャはエスケープしています。それがエスケープとマークしない理由です-エスケープしています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.