Как выполнить последовательный запрос с помощью Alamofire и обновить progressHUD на каждом этапе в Swift 3

Ладно, я схожу с ума по этому поводу...

Я использую Alamofire 4.x (Swift 3 и XCode 8.1). Мне нужно получить и проанализировать несколько html-запросов с сайта, требующего аутентификации (к сожалению, без json API). Затем HTML анализируется с помощью Fuzi, и этот прогресс может занять некоторое время, поэтому я планирую использовать ProgressHUD (точнее, PKHUD), чтобы пользователи знали о том, что происходит. Мне также нужно получить некоторый html, который не находится за аутентификацией.

Я создал структуру и функции для обработки всего сетевого процесса и анализа данных.

Мне удалось выполнить запросы и получить нужные мне данные, но я не могу понять, как обновлять HUD в нужное время.

Вот мой код:

import Alamofire
import Fuzi
import PKHUD

struct MyMSCProvider {

static let baseUrl = "http://mastersswimming.ca"

//I tried with or without a custom queue - same result
static let processingQueue = DispatchQueue(label: "com.colddiver.processing-queue", qos: .utility)

static func fetchData(data: MscRequest) {

    if data.profile || data.log {

        //Authenticate first!
        HUD.show(.labeledProgress(title: "Authenticating", subtitle: ""))

        let requestUrl = "\(baseUrl)/MyMscPage.jsp"
        let parameters = ["locale": "en", "username": data.user.username, "password": data.user.password]

        Alamofire.request(requestUrl, method: .post, parameters: parameters).responseData(
            queue: processingQueue,
            completionHandler:
            { response in


                // Now on the processingQueue you created earlier.
                print("THREAD: \(Thread.current) is main thread: \(Thread.isMainThread)")

                switch response.result {
                case .success:

                    if data.profile {
                        DispatchQueue.main.async {
                            HUD.show(.labeledProgress(title: "Getting Profile", subtitle: ""))
                        }
                        let userProfile = parseProfile(data: response.data!, user: data.user)
                        print(userProfile)
                    }

                    if data.log {
                        DispatchQueue.main.async {
                            HUD.show(.labeledProgress(title: "Getting Log", subtitle: ""))
                        }
                        fetchLog()
                    }

                    if data.records {
                        DispatchQueue.main.async {
                            HUD.show(.labeledProgress(title: "Getting Records", subtitle: ""))
                        }
                        fetchRecords(recordsToFetch: data.recordsToFetch)
                    }

                    if data.times {
                        DispatchQueue.main.async {
                            HUD.show(.labeledProgress(title: "Getting Times", subtitle: ""))
                        }
                        print("Fetching times is not implemented yet")
                    }

                    DispatchQueue.main.async {
                        HUD.flash(.success)
                    }


                case .failure(let error):
                    HUD.flash(.error)
                    print("Alamofire request failed")
                    print(error)
                }
        }
        )


    } else {
        //Just fetch - no need to authenticate first
        if data.records {
            DispatchQueue.main.async {
                HUD.show(.labeledProgress(title: "Getting Records", subtitle: ""))
            }
            fetchRecords(recordsToFetch: data.recordsToFetch)
        }

        if data.times {
            print("Fetching times is not implemented yet")
        }

        DispatchQueue.main.async {
            HUD.flash(.success)
        }
    }

}

static func fetchRecords(recordsToFetch: RecordsToFetch) {

    for province in recordsToFetch.provinces {
        for ageGroup in recordsToFetch.ageGroups {
            for gender in recordsToFetch.genders {

                DispatchQueue.main.async {
                    HUD.show(.labeledProgress(title: "Getting Records", subtitle: "\(province) - \(gender+Helpers.getAgeGroupFromAge(age: Int(ageGroup)!))"))
                }

                let requestUrl = "\(baseUrl)/Records.jsp"
                let parameters = ["locale": "en", "province": province, "age": ageGroup, "gender": gender, "course": "*"]

                Alamofire.request(requestUrl, method: .post, parameters: parameters).responseData(
                    queue: processingQueue,
                    completionHandler: { response in

                        switch response.result {
                        case .success:

                            let recordArray = parseRecords(data: response.data!, province: province, ageGroup: ageGroup, gender: gender)

                        case .failure(let error):
                            DispatchQueue.main.async {
                                HUD.flash(.failure)
                            }
                            print("Alamofire request failed")
                            print(error)
                        }
                }
                )
            }
        }
    }
}

static func fetchLog() {

    let requestUrl = "\(baseUrl)/ViewLog.jsp"

    Alamofire.request(requestUrl).responseData(
        queue: processingQueue,
        completionHandler: { response in

            switch response.result {
            case .success:
                let log = parseLog(data: response.data!)

            case .failure(let error):
                DispatchQueue.main.async {
                    HUD.flash(.failure)
                }
                print("Alamofire request failed")
            }
        }
    )
}

// MARK: - Convenience structs
struct MscRequest {
    let profile: Bool
    let log: Bool
    let times: Bool
    let records: Bool
    let recordsToFetch: RecordsToFetch
    let user: MscUser

    let parentView: UITableViewController
}

В этой настройке я бы установил MscRequest в TableViewController и запустил серию запросов следующим образом:

let myData = MscRequest.init(
  profile: true,
  log: true,
  times: false,
  records: true,
  recordsToFetch: RecordsToFetch.init(
    provinces: ["NB", "CA"],
    ageGroups: ["20", "25", "30", "35", "40"],
    genders: ["M", "F"]),
  user: MscUser.init(
    username: "SomeUserName",
    password: "SomePassword"),
  parentView: self
)

MyMSCProvider.fetchData(data: myData)

При такой настройке все обновления HUD выполняются одновременно (в основном потоке) и в конечном итоге отклоняются, в то время как фоновая выборка и синтаксический анализ все еще продолжаются. Не совсем то, к чему я стремился...

Я пробовал различные итерации (с пользовательской очередью или без нее), я также пробовал код запроса HTML прямо из руководства Alamofire (в котором отсутствует часть завершенияHandler), но я все равно получаю те же результаты...

Я также просмотрел учебные пособия по Grand Central Dispatch (например, этот: http://www.appcoda.com/grand-central-dispatch/), но я не понял, как применить информацию при использовании Alamofire...

Следует отметить, что тогда мне удалось сделать эту работу в Objective-C с помощью ручных NSURLRequests. Я модернизирую это старое приложение до Swift 3 и подумал, что должен попробовать Alamofire.

Не могу отделаться от ощущения, что я упускаю что-то очевидное... Какие-нибудь советы?


person Etienne Beaule    schedule 11.11.2016    source источник
comment
Хорошо, это (stackoverflow.com/questions/36911192/) намного ближе к тому, что мне нужно, но я еще не понял, как заставить его работать...   -  person Etienne Beaule    schedule 13.11.2016
comment
Выглядит так (stackoverflow.com/questions/28634995/ ) также может помочь - если не PromiseKit...   -  person Etienne Beaule    schedule 13.11.2016


Ответы (2)


Хорошо, я нашел способ делать то, что хочу, используя DispatchGroup (Swift 3, Alamofire 4.x)

func fetchData() {
    let requestGroup =  DispatchGroup()

    //Need as many of these statements as you have Alamofire.requests
    requestGroup.enter()
    requestGroup.enter()
    requestGroup.enter()

    Alamofire.request("http://httpbin.org/get").responseData { response in
        print("DEBUG: FIRST Request")
        requestGroup.leave()
    }

    Alamofire.request("http://httpbin.org/get").responseData { response in
         print("DEBUG: SECOND Request")
         requestGroup.leave()
    }

    Alamofire.request("http://httpbin.org/get").responseData { response in
         print("DEBUG: THIRD Request")
         requestGroup.leave()
    }

    //This only gets executed once all the above are done
    requestGroup.notify(queue: DispatchQueue.main, execute: {
        // Hide HUD, refresh data, etc.
         print("DEBUG: all Done")
    })

}
person Etienne Beaule    schedule 14.11.2016

Вы должны использовать DownloadRequest и использовать прогресс.

Также взгляните на этот пост, он объяснил:

POST-запрос Alamofire с ходом выполнения

person thierryb    schedule 11.11.2016
comment
Хм... Я не хочу показывать ход загрузки. Я хочу обновлять hud в начале каждого нового запроса, чтобы пользователь знал, что скачивается. Отдельный запрос выполняется довольно быстро, анализ html может занять некоторое время... - person Etienne Beaule; 12.11.2016