Синхронный запрос Alamofire с DispatchGroup

Мне нужно дождаться, пока запрос Alamofire завершит получение данных. (Ошибка или значение). Я вызываю функцию Alamofire внутри цикла for в другой функции, так что запросы Alamofire должны быть завершены до вызова второго цикла for. Например; первый цикл -> первый запрос -> второй цикл -> второй запрос... и так далее. Теперь идет первый цикл -> второй цикл -> и после того, как все циклы заканчиваются, запросы возвращаются.

Функция запроса:

 func like(sender_id: String, completion: @escaping (String?) -> ()){
    let group = DispatchGroup()
    if let key = api_key{
        let headers: HTTPHeaders = [
            "X-Auth-Token": key,
            "Accept": "application/json"
        ]
        group.enter()
        Alamofire.request(baseUrl + likeURL + sender_id, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).validate()
            .responseJSON { (response) -> Void in
                guard response.result.isSuccess else {
                    print("Error while doing : \(response.result.error)")
                    completion(nil)
                    group.leave()
                    return
                }
                if let error = response.error{
                    completion(nil)
                    group.leave()
                    print("Error Occured with Request: \(error)")
                }
                if let jsonData = response.data{
                    let json = JSON(data: jsonData)
                    print(json)
                    group.leave()
                    if let matched = json["match"].bool{
                        completion(matched.description)
                        print("Liked!: \(matched)")
                        if(matched){
                        }
                    }else{
                        group.leave()
                        "There is an error with JSON"
                    }
                }}
    }
}

Куда я звоню:

 func like_all(completion: @escaping() -> ()){
    for user in SharedUsers.sharedUser.users{
        if let id = user.id{
            Network.sharedInstance.like(sender_id: id) { result in
                print(result)
            }
        }
        else{
            continue
        }
    }
    completion()
}

person Mr Some Dev.    schedule 08.12.2017    source источник
comment
Независимо от того, в вашем обработчике завершения у вас есть путь выполнения, в котором вы дважды вызываете leave. Вы должны исправить это. Или, что лучше, если вы останетесь с этим шаблоном группы диспетчеризации, я бы предложил использовать defer { group.leave() } в начале закрытия, а затем вытащить все остальные вызовы leave, избегая любого риска иметь путь выполнения, где вы не можете вызвать leave. Честно говоря, это спорный вопрос, так как вы вообще не должны использовать группу диспетчеризации, а просто к вашему сведению.   -  person Rob    schedule 08.12.2017
comment
Я посмотрел promisekit, но я не уверен, что это правильный путь для моей проблемы.   -  person Mr Some Dev.    schedule 08.12.2017
comment
Ниже я описал два альтернативных подхода к тому, как это сделать, но PromiseKit определенно является еще одним способом решить эту проблему. Основная идея заключается в том, что вы никогда не захотите wait для какого-либо асинхронного процесса перед запуском другого, а используете один из этих шаблонов для асинхронного запуска одного запроса после завершения предыдущего. Это позволяет избежать блокировки потока (и устраняет любые риски взаимоблокировки).   -  person Rob    schedule 08.12.2017


Ответы (2)


Вы используете группу отправки, очевидно, с намерением дождаться группы в конце функции. Но вы этого не ждете, поэтому не получаете ожидаемого синхронного поведения.

Но это хорошо, потому что если вы wait для этой группы (или семафора, другого шаблона для достижения такого поведения) в основном потоке, вы не только заблокируете основной поток (что приводит к ужасному UX и рискует убить ваше приложение из-за watchdog), вы зайдете в тупик, потому что responseJSON использует основную очередь для своего обработчика завершения. Поэтому, прежде чем добавлять вызов wait() в группу/семафор диспетчера, убедитесь, что весь этот цикл for асинхронно отправляется в какой-нибудь фоновый поток. Это позволяет избежать блокировки основного потока и устраняет риск взаимоблокировки.

Но весь этот шаблон в корне ошибочен, так как вам действительно не следует использовать группы диспетчеризации или семафоры, чтобы сделать его синхронным. Это вызывает несколько вопросов:

Первый вопрос заключается в том, почему вы хотите сделать это синхронным. Сетевые запросы имеют присущую задержку, поэтому выполнение их последовательности будет очень медленным. Выполняйте эту последовательность запросов только в случае крайней необходимости (например, каждый запрос не может быть сформирован, потому что ему нужно что-то из ответа на предыдущий запрос). Но, похоже, это не тот случай. Так зачем делать этот процесс ненужным так.

Но давайте на секунду предположим, что вам абсолютно необходимо выполнять их последовательно (это не так, насколько я могу судить, но давайте подразним это). Затем есть два шаблона для выполнения серии запросов последовательно, а не одновременно:

  • Вы можете полностью отказаться от этого цикла for и просто иметь подпрограмму, которая отправляет n-й запрос и отправляет запрос n+1 в своем обработчике завершения. Это полностью устраняет необходимость в группах диспетчеризации/семафорах для блокировки потока.

  • Или вы можете обернуть это в операцию (например, https://stackoverflow.com/a/27022598/1271826) и использовать операцию очередь.

person Rob    schedule 08.12.2017
comment
Я хочу сделать это, потому что у меня есть класс пользователя, который заполняется моим первым запросом, а затем я начинаю делать второй запрос с информацией из класса пользователя. После возврата функции like_all она снова делает первый запрос, а затем снова like_all и т.д.... - person Mr Some Dev.; 08.12.2017
comment
Не могли бы вы привести пример для этого: вы можете полностью отказаться от этого цикла for и просто иметь подпрограмму, которая отправляет n-й запрос и отправляет запрос n+1 в своем обработчике завершения. Это полностью устраняет необходимость в группах/семафорах диспетчеризации для блокировки потока. - person Mr Some Dev.; 09.12.2017

Я решаю это, вызывая подобную функцию с индексом + 1 каждый раз, когда Alamofire возвращает значение. Я также вызываю функцию в завершающей части запроса на выборку.

Вот код:

    @objc func action(_ sender: LGButton) {
        sender.titleString = "Started to Like :)"
        Network.sharedInstance.get_rec(completion: { (result) in
            if(result != nil)
            {
                Network.sharedInstance.like(sender: 0, completion: { (result) in
                    //print(result)
                })
            }
        })
}

Нравится Функция:

 func like(sender: Int, completion: @escaping (String?) -> ()){
    if let key = api_key{
        let headers: HTTPHeaders = [
            "X-Auth-Token": key,
            "Accept": "application/json"
        ]
        print(sender)
        print(SharedUsers.sharedUser.users.count)
        if(sender < SharedUsers.sharedUser.users.count){
            if let user_id = SharedUsers.sharedUser.users[sender].id{
                Alamofire.request(baseUrl + likeURL + user_id, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).validate()
                    .responseJSON { (response) -> Void in
                        guard response.result.isSuccess else {
                            print("Error while doing : \(response.result.error)")
                            completion(nil)
                            return
                        }
                        if let error = response.error{
                            completion(nil)
                            print("Error Occured with Request: \(error)")
                        }
                        if let jsonData = response.data{
                            let json = JSON(data: jsonData)
                            if let matched = json["match"].bool{
                                completion(matched.description)
                                print("Liked!: \(matched)")
                                if(sender <= SharedUsers.sharedUser.users.count){
                                    self.like(sender: sender + 1, completion: {result in
                                        //print(result)
                                    })
                                }else{
                                    return
                                }
                                if(matched){
                                }
                            }else{
                                "There is an error with JSON"
                            }}}
            }else{return}
        }}}
person Mr Some Dev.    schedule 09.12.2017