AlamoFire asynchroner CompletionHandler für JSON-Anforderung

80

Nachdem ich das AlamoFire-Framework verwendet habe, habe ich festgestellt, dass der CompletionHandler auf dem Hauptthread ausgeführt wird. Ich frage mich, ob der folgende Code eine gute Vorgehensweise zum Erstellen einer Core Data-Importaufgabe im Completion-Handler ist:

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
quelle

Antworten:

156

Das ist eine wirklich gute Frage. Ihr Ansatz ist absolut gültig. Alamofire kann Ihnen jedoch dabei helfen, dies noch weiter zu optimieren.

Aufschlüsselung der Beispielcode-Versandwarteschlange

In Ihrem Beispielcode springen Sie zwischen den folgenden Versandwarteschlangen:

  1. NSURLSession-Versandwarteschlange
  2. TaskDelegate-Versandwarteschlange zur Validierung und Serialisierungsverarbeitung
  3. Hauptversandwarteschlange zum Aufrufen Ihres Completion-Handlers
  4. Warteschlange mit hoher Priorität für die JSON-Behandlung
  5. Hauptversandwarteschlange zum Aktualisieren der Benutzeroberfläche (falls erforderlich)

Wie Sie sehen können, hüpfen Sie überall herum. Werfen wir einen Blick auf einen alternativen Ansatz, der eine leistungsstarke Funktion in Alamofire nutzt.

Alamofire Response Dispatch Queues

Alamofire verfügt über einen optimalen Ansatz, der in die eigene Verarbeitung auf niedriger Ebene integriert ist. Die einzelne responseMethode, die letztendlich von allen benutzerdefinierten Antwortserialisierern aufgerufen wird, unterstützt eine benutzerdefinierte Versandwarteschlange, wenn Sie diese verwenden möchten.

Während GCD beim Wechseln zwischen Versandwarteschlangen erstaunlich ist, möchten Sie vermeiden, zu einer Warteschlange zu springen, die ausgelastet ist (z. B. zum Hauptthread). Indem Sie den Rücksprung zum Hauptthread während der asynchronen Verarbeitung eliminieren, können Sie die Dinge möglicherweise erheblich beschleunigen. Das folgende Beispiel zeigt, wie dies mithilfe der sofort einsatzbereiten Alamofire-Logik durchgeführt wird.

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

Aufschlüsselung der Alamofire-Versandwarteschlange

Hier ist die Aufschlüsselung der verschiedenen Versandwarteschlangen, die an diesem Ansatz beteiligt sind.

  1. NSURLSession-Versandwarteschlange
  2. TaskDelegate-Versandwarteschlange zur Validierung und Serialisierungsverarbeitung
  3. Gleichzeitige Versandwarteschlange des benutzerdefinierten Managers für die JSON-Verarbeitung
  4. Hauptversandwarteschlange zum Aktualisieren der Benutzeroberfläche (falls erforderlich)

Zusammenfassung

Indem Sie den ersten Sprung zurück zur Hauptversandwarteschlange eliminieren, haben Sie einen potenziellen Engpass beseitigt und Ihre gesamte Anforderung und Verarbeitung asynchron gemacht. Genial!

Trotzdem kann ich nicht genug betonen, wie wichtig es ist, sich mit den internen Funktionen von Alamofire vertraut zu machen. Sie wissen nie, wann Sie etwas finden, das Ihnen wirklich helfen kann, Ihren eigenen Code zu verbessern.

cnoon
quelle
3
Vielen Dank für die ausführliche Erklärung, @cnoon. Es scheint, dass der zweite Parameter für die responseMethode jetzt responseSerializereher aufgerufen wird als serializer(in Alamofire 3.0). Das verursachte eineCannot call value of non-function type 'NSHTTPURLResponse?' Fehler, der mich ein bisschen verwirrt hatte.
Hélène Martin
Bitte laden Sie die Änderungen hoch, der Code funktioniert nicht. Swift 2.1, XCode 7.1
Beraliv
Was ist mit responseJSON? Wie kann ich den Warteschlangenparameter übergeben?
OMGPOP
@cnoon, kann nett sein, wenn Sie Update für Swift 3 auch hinzufügen.
Mike.R
Funktioniert jetzt auch für Swift 3. Brilliant
dejavu89
2

Kleines Update für Swift 3.0, Alamofire (4.0.1), Bearbeiten für @cnoon Antwort:

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
quelle
1

Wenn Sie die perfekte Antwort von @cnoon ergänzen und mich verwenden möchten, ResponseObjectSerializablekönnen Sie dieses gleichzeitige Verhalten in die Anforderungserweiterung selbst einbetten:

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
quelle