Asynchroniczne uzupełnianie AlamoFire dla żądania JSON

80

Po użyciu frameworka AlamoFire zauważyłem, że CompleteHandler jest uruchamiany w głównym wątku. Zastanawiam się, czy poniższy kod jest dobrą praktyką do tworzenia zadania importu danych podstawowych w module obsługi zakończenia:

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
źródło

Odpowiedzi:

156

To naprawdę dobre pytanie. Twoje podejście jest całkowicie słuszne. Jednak Alamofire może faktycznie pomóc Ci to jeszcze bardziej usprawnić.

Twój przykładowy podział kolejki wysyłki kodu

W swoim przykładowym kodzie przeskakujesz między następującymi kolejkami wysyłania:

  1. Kolejka wysyłkowa NSURLSession
  2. Kolejka wysyłkowa TaskDelegate do weryfikacji i przetwarzania serializatora
  3. Główna kolejka wysyłkowa do wywoływania programu obsługi zakończenia
  4. Kolejka o wysokim priorytecie do obsługi formatu JSON
  5. Główna kolejka wysyłkowa do aktualizacji interfejsu użytkownika (w razie potrzeby)

Jak widać, skaczesz wszędzie. Przyjrzyjmy się alternatywnemu podejściu wykorzystującemu potężną funkcję wewnątrz Alamofire.

Kolejki wysyłek odpowiedzi Alamofire

Alamofire ma optymalne podejście wbudowane we własne przetwarzanie niskiego poziomu. Pojedyncza responsemetoda, która ostatecznie jest wywoływana przez wszystkie niestandardowe serializatory odpowiedzi, obsługuje niestandardową kolejkę wysyłania, jeśli zdecydujesz się jej użyć.

Chociaż GCD świetnie radzi sobie z przeskakiwaniem między kolejkami wysyłkowymi, chcesz uniknąć przeskakiwania do zajętej kolejki (np. Głównego wątku). Eliminując skok z powrotem do głównego wątku w środku przetwarzania asynchronicznego, możesz potencjalnie znacznie przyspieszyć działanie. Poniższy przykład ilustruje, jak to zrobić, używając logiki Alamofire od razu po wyjęciu z pudełka.

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

Podział kolejki wysyłki Alamofire

Oto zestawienie różnych kolejek wysyłek związanych z tym podejściem.

  1. Kolejka wysyłkowa NSURLSession
  2. Kolejka wysyłkowa TaskDelegate do weryfikacji i przetwarzania serializatora
  3. Niestandardowa współbieżna kolejka wysyłkowa menedżera do obsługi formatu JSON
  4. Główna kolejka wysyłkowa do aktualizacji interfejsu użytkownika (w razie potrzeby)

Podsumowanie

Eliminując pierwszy przeskok z powrotem do głównej kolejki wysyłania, wyeliminowałeś potencjalne wąskie gardło, a także sprawiłeś, że całe żądanie i przetwarzanie są asynchroniczne. Niesamowite!

Powiedziawszy to, nie mogę wystarczająco podkreślić, jak ważne jest poznanie wewnętrznych zasad tego, jak naprawdę działa Alamofire. Nigdy nie wiadomo, kiedy możesz znaleźć coś, co naprawdę pomoże Ci ulepszyć własny kod.

cnoon
źródło
3
Dzięki za dokładne wyjaśnienie, @cnoon. Wydaje się, że drugi parametr responsemetody jest teraz wywoływany responseSerializerzamiast serializer(w Alamofire 3.0). To spowodowało Cannot call value of non-function type 'NSHTTPURLResponse?'błąd, który trochę mnie zdezorientował.
Hélène Martin
proszę, prześlij zmiany, kod nie działa. Swift 2.1, XCode 7.1
Beraliv
a co z odpowiedziąJSON? Jak mogę przekazać parametr kolejki
OMGPOP
@cnoon, może być miło, jeśli dodasz również aktualizację dla Swift 3.
Mike.R
Działa teraz również dla Swift 3. Genialny
dejavu89
2

Mała aktualizacja dla Swift 3.0, Alamofire (4.0.1), Edycja dla @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
        }
    })
Mike.R
źródło
1

Uzupełniając idealną odpowiedź od @cnoon, jeśli lubisz mnie ResponseObjectSerializable, możesz osadzić to współbieżne zachowanie w samym rozszerzeniu żądania:

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
źródło