web-dev-qa-db-fra.com

Requête asynchrone AlamoFire pour demande JSON

Ayant utilisé le framework AlamoFire, j'ai remarqué que le completionHandler est exécuté sur le thread principal. Je me demande si le code ci-dessous est une bonne pratique pour créer une tâche d'importation Core Data dans le gestionnaire d'achèvement:

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

                    }
                });
            }
77
TheM00s3

C'est une très bonne question. Votre approche est parfaitement valide. Cependant, Alamofire peut réellement vous aider à rationaliser encore plus.

Votre exemple de répartition de la file d'attente de distribution de code

Dans votre exemple de code, vous passez d'une file d'attente à une autre:

  1. File d'attente de distribution NSURLSession
  2. File d'attente de distribution TaskDelegate pour la validation et le traitement du sérialiseur
  3. File d'attente principale pour appeler votre gestionnaire d'achèvement
  4. File d'attente haute priorité pour la gestion JSON
  5. File d'attente principale pour la mise à jour de l'interface utilisateur (si nécessaire)

Comme vous pouvez le voir, vous sautez partout. Jetons un coup d'œil à une approche alternative exploitant une fonctionnalité puissante d'Alamofire.

Alamofire Response Dispatch Files d'attente

Alamofire a une approche optimale intégrée à son propre traitement de bas niveau. La méthode unique response qui est finalement appelée par tous les sérialiseurs de réponses personnalisées prend en charge une file d’attribution personnalisée si vous choisissez de l’utiliser.

Bien que GCD permette de passer d’une file d’attente de répartition à une autre, vous voulez éviter de passer à une file d’attente occupée (par exemple, le thread principal). En éliminant le retour au thread principal au milieu du traitement async, vous pouvez potentiellement accélérer considérablement les choses. L'exemple suivant montre comment faire cela en utilisant la logique Alamofire directement et immédiatement.

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 et 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 Dispatch Queue Breakdown

Voici la répartition des différentes files d’attribution impliquées dans cette approche.

  1. File d'attente de distribution NSURLSession
  2. File d'attente de distribution TaskDelegate pour la validation et le traitement du sérialiseur
  3. File d'attente de répartition simultanée du gestionnaire personnalisé pour la gestion JSON
  4. File d'attente principale pour la mise à jour de l'interface utilisateur (si nécessaire)

Sommaire

En éliminant le premier saut dans la file d'attente de répartition principale, vous avez éliminé tout goulot d'étranglement potentiel et rendu l'ensemble de votre demande et de votre traitement asynchrones. Impressionnant!

Cela dit, je ne saurais trop insister sur l’importance de se familiariser avec le fonctionnement interne d’Alamofire. Vous ne savez jamais quand vous pouvez trouver quelque chose qui peut vraiment vous aider à améliorer votre propre code.

154
cnoon

Petite mise à jour pour Swift 3.0, Alamofire (4.0.1), Edit for @cnoon answer:

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
Mike.R

Pour compléter simplement la réponse parfaite de @cnoon, si vous aimez bien utiliser ResponseObjectSerializable, vous pouvez intégrer ce comportement simultané à l'extension de requête elle-même:

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
Raphael Oliveira