Аланофир-асинхронное завершениеHandler для запроса JSON

Используя структуру AlamoFire, я заметил, что функция завершенияHandler запускается в основном потоке. Мне интересно, является ли приведенный ниже код хорошей практикой для создания задачи импорта основных данных в обработчике завершения:

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

                    }
                });
            }

Ответ 1

Это действительно хороший вопрос. Ваш подход совершенно верен. Тем не менее, Alamofire действительно может помочь вам оптимизировать это еще больше.

Отказ от очереди отправки кода кода

В вашем примере кода вы прыгаете между очередями отправки:

  • Очередь отправки NSURLSession
  • TaskDelegate диспетчерская очередь для проверки и обработки сериализатора
  • Основная очередь отправки для вызова обработчика завершения.
  • Очередь с высоким приоритетом для обработки JSON
  • Основная очередь отправки для обновления пользовательского интерфейса (при необходимости)

Как вы можете видеть, вы прыгаете повсюду. Позвольте взглянуть на альтернативный подход, используя мощную функцию внутри 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

Вот разбивка различных очередей отправки, связанных с этим подходом.

  • Очередь отправки NSURLSession
  • TaskDelegate диспетчерская очередь для проверки и обработки сериализатора
  • Пользовательская диспетчерская очередь для диспетчеризации для обработки JSON
  • Основная очередь отправки для обновления пользовательского интерфейса (при необходимости)

Резюме

Исключив первый переход обратно в основную очередь отправки, вы устранили потенциальное узкое место, а также сделали свой весь запрос и обработали асинхронно. Отлично!

С учетом сказанного я не могу достаточно подчеркнуть, насколько важно познакомиться с внутренними аспектами того, как Alamofire действительно работает. Вы никогда не знаете, когда найдете что-то, что действительно поможет вам улучшить свой собственный код.

Ответ 2

Небольшое обновление для Swift 3.0, Alamofire (4.0.1), Edit для ответа @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
        }
    })

Ответ 3

Просто дополняя идеальный ответ от @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)
            }
        }
    }
}