Manejador de finalización asincrónico AlamoFire para solicitud JSON

80

Después de haber usado el marco AlamoFire, he notado que completeHandler 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);

                    }
                });
            }
TheM00s3
fuente

Respuestas:

156

Esta es una muy buena pregunta. Tu enfoque es perfectamente válido. Sin embargo, Alamofire realmente puede ayudarlo a simplificar esto aún más.

Su código de ejemplo Desglose de la cola de despacho

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

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

Como puede ver, está saltando por todos lados. Echemos un vistazo a un enfoque alternativo que aprovecha una característica poderosa dentro de Alamofire.

Colas de despacho de respuesta de Alamofire

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

Si bien GCD es increíble para saltar entre colas de despacho, desea evitar saltar a una cola que está ocupada (por ejemplo, el hilo principal). Al eliminar el salto de regreso al hilo principal en medio del procesamiento asincrónico, potencialmente puede acelerar las cosas considerablemente. El siguiente ejemplo demuestra cómo hacer esto usando la lógica de Alamofire directamente desde el primer momento.

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 cola de despacho para validación y procesamiento de serializador
  3. Cola de despacho concurrente del administrador personalizado para el manejo de JSON
  4. Cola de envío principal para actualizar la interfaz de usuario (si es necesario)

Resumen

Al eliminar el primer salto de regreso a la cola de despacho principal, ha eliminado un posible cuello de botella y ha hecho que toda su solicitud y procesamiento sean asincrónicos. ¡Increíble!

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

por la tarde
fuente
3
Gracias por la explicación detallada, @cnoon. Parece que el segundo parámetro del responsemétodo ahora se llama en responseSerializerlugar de serializer(en Alamofire 3.0). Eso provocó un Cannot call value of non-function type 'NSHTTPURLResponse?'error que me confundió un poco.
Hélène Martin
por favor, cargue los cambios, el código no funciona. Swift 2.1, XCode 7.1
Beraliv
¿qué pasa con responseJSON? ¿Cómo puedo pasar el parámetro de cola
OMGPOP
@cnoon, puede ser bueno si agrega la actualización para swift 3 también.
Mike.R
También funciona ahora para Swift 3. Brillante
dejavu89
2

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
        }
    })
Mike.R
fuente
1

Simplemente complementando la respuesta perfecta de @cnoon, si me gusta ResponseObjectSerializable, puede incrustar este comportamiento concurrente en la extensión de 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)
            }
        }
    }
}
Raphael Oliveira
fuente