JSONリクエストのAlamoFire非同期completionHandler


80

AlamoFireフレームワークを使用した後、completionHandlerがメインスレッドで実行されていることに気付きました。以下のコードが、完了ハンドラー内でCoreDataインポートタスクを作成するための優れたプラクティスであるかどうか疑問に思っています。

Alamofire.request(.GET, "http://myWebSite.com", parameters: parameters)
            .responseJSON(options: .MutableContainers) { (_, _, JSON, error) -> Void in
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
                    if let err = error{
                        println("Error:\(error)")
                        return;
                    }

                    if let jsonArray = JSON as? [NSArray]{                       
                        let importer = CDImporter(incomingArray: jsonArray entity: "Artist", map: artistEntityMap);

                    }
                });
            }

回答:


156

これは本当に良い質問です。あなたのアプローチは完全に有効です。ただし、Alamofireは、実際にはこれをさらに合理化するのに役立ちます。

サンプルコードディスパッチキューの内訳

サンプルコードでは、次のディスパッチキュー間をジャンプしています。

  1. NSURLSessionディスパッチキュー
  2. 検証およびシリアライザー処理のためのTaskDelegateディスパッチキュー
  3. 完了ハンドラーを呼び出すためのメインディスパッチキュー
  4. JSON処理用の優先度の高いキュー
  5. ユーザーインターフェイスを更新するためのメインディスパッチキュー(必要な場合)

ご覧のとおり、あちこちを飛び回っています。Alamofire内の強力な機能を活用する代替アプローチを見てみましょう。

Alamofire応答ディスパッチキュー

Alamofireには、独自の低レベル処理に最適なアプローチが組み込まれています。response最終的にすべてのカスタム応答シリアライザーによって呼び出される単一のメソッドは、それを使用することを選択した場合、カスタムディスパッチキューをサポートします。

GCDはディスパッチキュー間のホッピングに優れていますが、ビジー状態のキュー(メインスレッドなど)にジャンプすることは避けたいと考えています。非同期処理の途中でメインスレッドに戻るジャンプを排除することで、処理を大幅に高速化できる可能性があります。次の例は、Alamofireロジックをすぐに使用してこれを行う方法を示しています。

Alamofire 1.x

let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)

let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
    queue: queue,
    serializer: Request.JSONResponseSerializer(options: .AllowFragments),
    completionHandler: { _, _, JSON, _ in

        // You are now running on the concurrent `queue` you created earlier.
        println("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")

        // Validate your JSON response and convert into model objects if necessary
        println(JSON)

        // To update anything on the main thread, just jump back on like so.
        dispatch_async(dispatch_get_main_queue()) {
            println("Am I back on the main thread: \(NSThread.isMainThread())")
        }
    }
)

Alamofire 3.x(Swift 2.2および2.3)

let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)

let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
    queue: queue,
    responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments),
    completionHandler: { response in
        // You are now running on the concurrent `queue` you created earlier.
        print("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")

        // Validate your JSON response and convert into model objects if necessary
        print(response.result.value)

        // To update anything on the main thread, just jump back on like so.
        dispatch_async(dispatch_get_main_queue()) {
            print("Am I back on the main thread: \(NSThread.isMainThread())")
        }
    }
)

Alamofire 4.x(Swift 3)

let queue = DispatchQueue(label: "com.cnoon.response-queue", qos: .utility, attributes: [.concurrent])

Alamofire.request("http://httpbin.org/get", parameters: ["foo": "bar"])
    .response(
        queue: queue,
        responseSerializer: DataRequest.jsonResponseSerializer(),
        completionHandler: { response in
            // You are now running on the concurrent `queue` you created earlier.
            print("Parsing JSON on thread: \(Thread.current) is main thread: \(Thread.isMainThread)")

            // Validate your JSON response and convert into model objects if necessary
            print(response.result.value)

            // To update anything on the main thread, just jump back on like so.
            DispatchQueue.main.async {
                print("Am I back on the main thread: \(Thread.isMainThread)")
            }
        }
    )

Alamofireディスパッチキューの内訳

このアプローチに関連するさまざまなディスパッチキューの内訳は次のとおりです。

  1. NSURLSessionディスパッチキュー
  2. 検証およびシリアライザー処理のためのTaskDelegateディスパッチキュー
  3. JSON処理用のカスタムマネージャー同時ディスパッチキュー
  4. ユーザーインターフェイスを更新するためのメインディスパッチキュー(必要な場合)

概要

メインディスパッチキューに戻る最初のホップを排除することで、潜在的なボトルネックを排除し、リクエスト全体と処理を非同期にしました。驚くばかり!

そうは言っても、Alamofireが実際にどのように機能するかについての内部に精通することがどれほど重要であるかを十分に強調することはできません。自分のコードを改善するのに本当に役立つ何かをいつ見つけることができるかはわかりません。


3
徹底的な説明をありがとう、@ cnoon。responseメソッドの2番目のパラメーターresponseSerializerserializer(Alamofire 3.0では)ではなく呼び出されるようになりました。それCannot call value of non-function type 'NSHTTPURLResponse?'は私を少し混乱させたエラーを引き起こしました。
エレーヌ・マーティン

変更をアップロードしてください。コードは機能しません。Swift 2.1、XCode 7.1
Beraliv 2015

responseJSONはどうですか?キューパラメータを渡すにはどうすればよいですか
OMGPOP 2016年

@ cnoon、swift3のアップデートも追加すると便利です。
Mike.R 2016年

Swift3でも動作するようになりました。ブリリアント
dejavu89 2016年

2

Swift 3.0の小さな更新、Alamofire(4.0.1)、@ cnoonの回答の編集:

let queue = DispatchQueue(label: "com.cnoon.manager-response-queue",
                          qos: .userInitiated,
                          attributes:.concurrent)
Alamofire?.request(SERVER_URL, method: .post,
parameters: ["foo": "bar"], 
encoding: JSONEncoding.default,//by default
headers: ["Content-Type":"application/json; charset=UTF-8"])
.validate(statusCode: 200..<300).//by default
responseJSON(queue: queue, options: .allowFragments, 
completionHandler: { (response:DataResponse<Any>) in

        switch(response.result) {
        case .success(_):
            break
        case .failure(_):
            print(response.result.error)
            if response.result.error?._code == NSURLErrorTimedOut{
                //TODO: Show Alert view on netwok connection.
            }
            break
        }
    })

1

@cnoonからの完璧な答えを補完するだけで、私のように使用しているResponseObjectSerializable場合は、この同時動作をリクエスト拡張機能自体に埋め込むことができます。

extension Request {
    public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
        let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
            guard error == nil else { return .Failure(error!) }

            let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let result = JSONResponseSerializer.serializeResponse(request, response, data, error)

            switch result {
            case .Success(let value):
                if let
                    response = response,
                    responseObject = T(response: response, representation: value)
                {
                    return .Success(responseObject)
                } else {
                    let failureReason = "JSON could not be serialized into response object: \(value)"
                    let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
                    return .Failure(error)
                }
            case .Failure(let error):
                return .Failure(error)
            }
        }

        let queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT)
        return response(queue: queue, responseSerializer: responseSerializer) { response in
            dispatch_async(dispatch_get_main_queue()) {
                completionHandler(response)
            }
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.