Controlador de terminación asíncrono AlamoFire para solicitud JSON


Después de haber usado el framework AlamoFire he notado que el controlador completionHandler se ejecuta en el hilo principal. Me pregunto si el siguiente código es una buena práctica para crear una tarea de importación de datos básicos dentro del controlador de finalización:

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

                    }
                });
            }
Author: TheM00s3, 2015-04-24

3 answers

Esta es una muy buena pregunta. Su enfoque es perfectamente válido. Sin embargo, Alamofire puede ayudarte a optimizar esto aún más.

Su Desglose de La Cola de Envío de Código de Ejemplo

En su código de ejemplo, está saltando entre las siguientes colas de despacho:

  1. Cola de despacho NSURLSession
  2. TaskDelegate la cola de despacho para la validación y el procesamiento del serializador
  3. Cola de despacho principal para llamar a su controlador de finalización
  4. Alta cola de prioridad para el manejo de JSON
  5. Cola de despacho principal para actualizar la interfaz de usuario (si es necesario)

Como puedes ver, estás saltando por todas partes. Echemos un vistazo a un enfoque alternativo aprovechando una poderosa característica dentro de Alamofire.

Colas de Envío de Respuesta de Alamofire

Alamofire tiene un enfoque óptimo integrado en su propio procesamiento de bajo nivel. El único método response que en última instancia es llamado por todos los serializadores de respuesta personalizada tiene soporte para una cola de despacho personalizada si elige usarla.

Si bien GCD es increíble para saltar entre colas de despacho, debe evitar saltar a una cola que esté ocupada (por ejemplo, el hilo principal). Al eliminar el salto al hilo principal en medio del procesamiento asíncrono, puede acelerar las cosas considerablemente. El siguiente ejemplo muestra cómo hacer esto usando Alamofire logic directamente fuera de la caja.

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

Desglose de la Cola de Despacho de Alamofire

Aquí está el desglose de las diferentes colas de despacho involucradas con este enfoque.

  1. Cola de despacho NSURLSession
  2. TaskDelegate la cola de despacho para la validación y el procesamiento del serializador
  3. Custom manager concurrent dispatch queue for JSON handling
  4. Cola de despacho principal para actualizar la interfaz de usuario (si necesario)

Resumen

Al eliminar el primer salto de vuelta a la cola de despacho principal, ha eliminado un cuello de botella potencial, así como ha hecho que toda su solicitud y el procesamiento sea asincrónico. ¡Órale!

Dicho esto, no puedo enfatizar lo suficiente lo importante que es familiarizarse con los aspectos internos de cómo Alamofire realmente funciona. Nunca se sabe cuando se puede encontrar algo que realmente puede ayudar a mejorar su propio código.

 143
Author: cnoon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-02 18:24:09

Pequeña actualización para Swift 3.0, Alamofire (4.0.1), Editar para @cnoon respuesta:

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
        }
    })
 2
Author: Mike.R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-02 12:05:49

Simplemente complementando la respuesta perfecta de @cnoon, si te gusta me está usando ResponseObjectSerializable puedes incrustar este comportamiento concurrente en la propia extensión de la solicitud:

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)
            }
        }
    }
}
 1
Author: Raphael Oliveira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-27 01:12:39