web-dev-qa-db-ja.com

迅速な@エスケープと補完ハンドラ

私はSwiftの「閉鎖」をもっと正確に理解しようとしています。

しかし@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)

私は@escapingを使用する2つの方法と理由があると聞いた

1つ目はクロージャーを格納するためのもの、2つ目はAsync操作の目的用です。

以下が私の質問です

まず、doSomethingが実行されると、someFunctionWithEscapingClosureはクロージャパラメータで実行され、そのクロージャはグローバル変数配列に保存されます。

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

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

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

ローカル変数クロージャcompletionHandlerをグローバル変数 'c​​ompletionHandlerswe using @ escaping`に格納するには!

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

@escapingはクロージャをメモリに保持する

これは正解?

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

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

特定の機能の後に何らかの機能を実行したい場合。特定の関数呼び出しの後に単に関数を呼び出すのではないのですか。

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

70
Dongkun Lee

まず最初に、「とても良い質問:)」と言いたいです。

完了ハンドラ:

ユーザーがアプリの使用中にアプリを更新しているとします。それが完了したら、間違いなくユーザーに通知したいです。 「おめでとうございます、今、あなたは完全に楽しむことができます!」というボックスをポップアップ表示したいと思うかもしれません。

では、ダウンロードが完了した後にのみどのようにしてコードブロックを実行するのですか。さらに、View Controllerを次のオブジェクトに移動した後にのみ、どのように特定のオブジェクトをアニメートするのですか。まあ、私たちはボスのようにデザインする方法を見つけようとしています。私の広範な語彙リストに基づいて、補完ハンドラはの略

何かが終わったら

詳しくは このブログ記事 をご覧ください。

このリンクは補完ハンドラについての完全な明快さを私に与えます(開発者の観点からそれは私たちが理解する必要があるものを正確に定義します)。

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

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

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

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

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

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

error screenshot

このトピックについてより明確にするために、 この投稿をMediumにチェックアウトすることができます

このリンクからあなたが良い理解を得られることを願っています。

それでも質問がある場合(ただし、このリンクを最初に1行ずつ読むようにしてください。非常によく説明されています)、コメントを共有してください。

この回答を更新する必要がある場合は、投稿し続けてください

88

これは@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()
        }
    }

}
18
JamesK

機能

funcキーワードを使って関数を定義します。関数は多くのパラメータを取り、何も、1つまたは複数のパラメータを返すことができます。

クロージャ

クロージャは、コード内で受け渡して使用できる自己完結型の機能ブロックです。 Swiftのクロージャは、CおよびObjective-Cのブロック、および他のプログラミング言語のラムダに似ています。

関数とクロージャはSwiftの最初のクラス型です:

  • 関数/クロージャをローカル変数に代入する
  • 引数として関数/クロージャを渡す
  • 関数/クロージャを返す

エスケープクロージャーと非エスケープクロージャー

  • non-escaping closure@noescapeは、渡された関数内、つまり戻る前に呼び出されるクロージャです。

  • escaping closure@escapingは、渡された関数が戻った後に呼び出されるクロージャです。言い換えれば、渡された関数より長生きします。これに関する一般的な使用例は次のとおりです。

    • 非同期呼び出しネットワーキング。
    • 関数は変数として格納されています。アクションを考えてコールバックを提供します。
    • ディスパッチ待ち行列でのタスクのスケジューリング.

* @noescapeはSwift2の属性です。これはSwift3から廃止予定です。 Swift3では、@noescape属性がデフォルトで適用されます。 Swift3ではクロージャはデフォルトでエスケープされていないため、エスケープクロージャはそのようにマークする必要があります。そして@escaping属性を使えばそれが可能になります。

補完ハンドラ

escaping closureの良い例はcompletion handlerです。非同期操作を開始する多くの関数は、完了ハンドラーとしてクロージャー引数を取ります。関数は操作開始後に戻りますが、操作が完了するまでクロージャーは呼び出されません。クロージャーは後で呼び出すためにエスケープする必要があります。

ここで詳細を読む - ミディアムポストミディアムポストドキュメント

1
yoAlex5
import UIKit
import Alamofire

//モデル

class ShortlistCountResponse : Decodable {
    var response : String?
    var data : ShortlistcountData?

}
class ShortlistcountData : Decodable {

    var totalpropFavcount : Int?
    var totalprojFavcount : Int?

}

//ジェネリッククラスの定義......

static func fetchGenericData<T: Decodable>(urlString: String,params : [String:Any], completion: @escaping (T) -> ()) {
        let url = urlString
        let headers = ["Content-Type": "application/x-www-form-urlencoded", "Accept":"application/json"]
        Alamofire.request(url, method: .post, parameters:params, encoding: URLEncoding.default, headers: headers).responseJSON { response in
            print(response.request?.urlRequest ?? "")
            print(params)
            print(response.data ?? "")
            print(response.value ?? "")
            switch(response.result) {
            case .success(_):
                if let data = response.data{
                    do {
                        let gotData = try JSONDecoder().decode(T.self, from: data)
                        completion(gotData)

                    }
                    catch let jsonErr {
                        print("Error serializing json:", jsonErr)
                        ActivityIndicator.dismissActivityView()

                    }
                    DispatchQueue.main.async {
                        ActivityIndicator.dismissActivityView()
                    }
                }
                break
            case .failure(_):
                print(response.result.error ?? "")
                ActivityIndicator.dismissActivityView()


                break

            }
        }
}

//楽しい電話

override func viewDidLoad() {
    super.viewDidLoad()

            let userID = ""
            let languageID = ""
            let params = ["userID":userID,"languageID":languageID]
            var appDelegate: AppDelegate?
            Service.fetchGenericData(urlString: "your url...", params: params) { (shortlistCountResponse : ShortlistCountResponse) in
             print(shortListCountResponse.data.totalprojFavcount ?? 0)

            }
}
0
kiran pm