Swift @escapingおよびCompletion Handler


98

スウィフトの「クロージャ」をより正確に理解しようとしています。

しかし@escapingCompletion Handler理解するのが難しすぎる

私は多くのSwiftの投稿や公式文書を検索しましたが、それでもまだ十分ではないと感じました。

これは公式文書のコード例です

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

使用には2つの方法と理由があると聞きました @escaping

1つはクロージャの格納用、2つ目は非同期操作用です。

以下は私の質問です:

まず、doSomethingexecute someFunctionWithEscapingClosureを実行すると、クロージャーパラメーターを使用して実行され、そのクロージャーはグローバル変数配列に保存されます。

閉鎖は{self.x = 100}だと思います

selfグローバル変数に保存された{self.x = 100}ではcompletionHandlersinstanceそのオブジェクトにどのように接続できますSomeClassか?

第二に、私someFunctionWithEscapingClosureはこのように理解しています。

ローカル変数のクロージャcompletionHandlerをグローバル変数の 'completionHandlers we using@ escaping`キーワードに保存するには!

@escapingキーワードsomeFunctionWithEscapingClosureが返されない場合、ローカル変数completionHandlerはメモリから削除されます

@escaping その閉鎖を記憶に留めておくことです

これは正解?

最後に、この文法の存在について疑問に思います。

多分これは非常に初歩的な質問です。

特定の関数の後に関数を実行させたい場合。特定の関数呼び出しの後に関数を呼び出さないのはなぜですか?

上記のパターンの使用とエスケープコールバック関数の使用の違いは何ですか?

回答:


122

Swift完了ハンドラーのエスケープと非エスケープ:

Bob Leeが彼のブログ投稿でBobとSwiftのCompletion Handlersを説明しているように:

ユーザーがアプリの使用中にアプリを更新しているとします。完了したら必ずユーザーに通知する必要があります。「おめでとうございます。これで十分にお楽しみいただけます!」というボックスをポップアップすることをお勧めします。

それで、ダウンロードが完了した後にのみ、コードブロックをどのように実行しますか?さらに、ビューコントローラーが次のオブジェクトに移動した後でのみ、特定のオブジェクトをどのようにアニメーション化しますか?さて、私たちは上司のようなものを設計する方法を見つけようとしています。

私の広範な語彙リストに基づいて、完了ハンドラーは

物事が行われたときに何かをする

Bobの投稿では、完了ハンドラについて明確に説明しています(開発者の観点からは、理解する必要があるものを正確に定義しています)。

@エスケープクロージャ:

関数の引数にクロージャを渡すと、関数の本体が実行され、コンパイラが返された後でクロージャを使用します。関数が終了すると、渡されたクロージャのスコープが存在し、クロージャが実行されるまでメモリに存在します。

包含関数でクロージャをエスケープする方法はいくつかあります。

  • ストレージ:クロージャーをグローバル変数に格納する必要がある場合、プロパティまたは呼び出し元の関数の過去のメモリに存在するその他のストレージが実行され、コンパイラーを返します。

  • 非同期実行:ディスパッチキューで非同期にクロージャーを実行すると、キューはクロージャーをメモリに保持し、将来使用できるようになります。この場合、クロージャがいつ実行されるかはわかりません。

これらのシナリオでクロージャーを使用しようとすると、Swiftコンパイラーがエラーを表示します。

エラーのスクリーンショット

このトピックをより明確にするために、Mediumのこの投稿をチェックしてください。

すべてのiOS開発者が理解する必要があるポイントをもう1つ追加します。

  1. エスケープクロージャ:エスケープクロージャは、渡された関数が戻った後に呼び出されるクロージャです。つまり、渡された関数よりも長く存続します。
  2. 非エスケープクロージャ:渡された関数内で、つまり戻る前に呼び出されるクロージャ。

@shabhakar、クロージャーを格納しているが後で呼び出さない場合 または、メソッドが2回呼び出されても、クロージャーを1回だけ呼び出した場合。結果は同じであることを知っているので。
user1101733

@ user1101733クロージャのエスケープについて話していると思います。クロージャは、呼び出さない限り実行されません。上記の例では、doSomethingメソッドを2回呼び出すと、2つのcompletedHandlerオブジェクトがcompletionHandlers配列に追加されます。completionHandlers配列から最初のオブジェクトを取得して呼び出すと、実行されますが、completementHandlers配列の数は同じままになります(2)。
ディーパック2018

@Deepak、エスケープクロージャについてはい。配列を使用せず、通常の変数を使用してクロージャー参照を格納するとします。これは、最新の呼び出しを実行するためです。呼び出すことのない以前のクロージャーによってメモリが占​​有されますか?
user1101733

1
@ user1101733クロージャは参照型(クラスのような)であり、新しいクロージャを変数に割り当てると、プロパティ/変数は新しいクロージャを指すため、ARCは以前のクロージャのメモリを割り当て解除します。
ディーパック2018

28

@escapingがどのように機能するかを思い出させるために使用するいくつかの例の例を次に示します。

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}

2
このコードは正しくありません。@escaping修飾子がありません。
Rob

私はこれが一番好きでしたi.e. escape the scope of this function.
Gal
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.