Асинхронний завершувач AlamoFire для обробки запиту JSON


80

Використовуючи фреймворк AlamoFire, я помітив, що fillHandler запускається в основному потоці. Мені цікаво, чи наведений нижче код є гарною практикою для створення завдання імпорту основних даних в обробнику завершення:

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

                    }
                });
            }

Відповіді:


156

Це справді гарне запитання. Ваш підхід цілком справедливий. Однак Alamofire насправді може допомогти вам ще більше впорядкувати це.

Ваш приклад розподілу черги відправлення коду

У вашому прикладі коду ви переходите між такими чергами відправлення:

  1. Черга відправлення NSURLSession
  2. TaskDelegate черга відправлення для перевірки та обробки серіалізатора
  3. Основна черга відправлення для виклику обробника завершення
  4. Черга з високим пріоритетом для обробки JSON
  5. Головна черга відправлення для оновлення інтерфейсу користувача (за необхідності)

Як бачите, ви стрибаєте всюди. Давайте подивимось на альтернативний підхід, що використовує потужну функцію всередині Alamofire.

Черги відправлення відповіді Alamofire

Alamofire має оптимальний підхід, вбудований у власну обробку на низькому рівні. Єдиний responseметод, який в кінцевому підсумку викликається усіма спеціальними серіалізаторами відповідей, має підтримку власної черги відправлення, якщо ви вирішите її використовувати.

Хоча GCD дивовижно перестрибує між чергами відправлення, ви хочете уникнути переходу до черги, яка зайнята (наприклад, основний потік). Усунувши перехід до основного потоку в середині асинхронної обробки, ви можете значно пришвидшити процес. Наступний приклад демонструє, як це зробити за допомогою логіки Alamofire прямо з коробки.

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 та 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 (Свіфт 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

Ось розподіл різних черг відправлення, що беруть участь у цьому підході.

  1. Черга відправлення NSURLSession
  2. TaskDelegate черга відправлення для перевірки та обробки серіалізатора
  3. Черга одночасного диспетчерського керування для обробки JSON
  4. Головна черга відправлення для оновлення інтерфейсу користувача (за необхідності)

Резюме

Усунувши перший стрибок назад до основної черги відправлення, ви усунули потенційне вузьке місце, а також зробили весь запит та обробку асинхронними. Чудово!

З урахуванням сказаного, я не можу підкреслити, наскільки важливо ознайомитись із внутрішніми характеристиками того, як насправді працює Alamofire. Ви ніколи не знаєте, коли ви можете знайти щось, що справді може допомогти вам вдосконалити ваш власний код.


3
Дякуємо за ретельне пояснення, @cnoon. Здається, що другий параметр responseметоду зараз називається, responseSerializerа не serializer(в Alamofire 3.0). Це спричинило Cannot call value of non-function type 'NSHTTPURLResponse?'помилку, яка мене трохи заплутала.
Hélène Martin

будь-ласка, завантажте зміни, код не працює. Свіфт 2.1, XCode 7.1
Бералів,

як щодо відповідіJSON? Як я можу передати параметр черги
OMGPOP

@cnoon, може бути приємно, якщо ви також додасте оновлення для swift 3.
Mike.R

Зараз працює і для Swift 3. Блискуче
dejavu89

2

Невелике оновлення для Swift 3.0, Alamofire (4.0.1), редагування для відповіді @cnoon:

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

1

Просто доповнюючи ідеальну відповідь від @cnoon, якщо вам подобається я, ResponseObjectSerializableви можете вбудувати цю паралельну поведінку в саме розширення запиту:

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)
            }
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.