0

Ok, I am going nuts over this one...

I'm using Alamofire 4.x (Swift 3 and XCode 8.1). I need to fetch and parse several html requests from a site that requires authentication (no json API, unfortunately). The HTML is then parsed with Fuzi and this progress can take some time so I plan to use a ProgressHUD (PKHUD to be exact) to let users know about what is going on. I also need to grab some html that is not behind an authentication.

I created a struct and functions to handle the overall network process and to parse the data.

I managed to perform the requests and grab the data I need but I can't seem to figure out how to make my HUD updates at the right time.

Here is my code so far:

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
}

Under this setup, I would setup a MscRequest in a TableViewController and launch a series a requests like so:

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)

With this setup, all the HUD updates are done at the same time (on the main thread) and end up being dismissed while the background fetching and parsing is still going on. Not exactly what I was going for...

I tried various iterations (with or without a custom queue), I also tried the HTML request code straight from Alamofire's manual (which omits the completionHandler part) but I still get the same results...

I also had a look at Grand Central Dispatch tutorials (such as this one: http://www.appcoda.com/grand-central-dispatch/) but I haven't figured out how to apply info when using Alamofire...

Of note, I managed to make this work in Objective-C with manual NSURLRequests back then. I'm modernizing this old application to Swift 3 and thought I should give Alamofire a try.

Can't help to feel like I am missing something obvious... Any tips?

Etienne Beaule
  • 207
  • 2
  • 10
  • Ok, this (http://stackoverflow.com/questions/36911192/how-to-load-view-after-alamofire-finished-its-job) is much closer to what I think I need but I haven't figured out how to make it work yet... – Etienne Beaule Nov 13 '16 at 14:12
  • Looks like this (http://stackoverflow.com/questions/28634995/chain-multiple-alamofire-requests) may also be of help - if not perhaps PromiseKit... – Etienne Beaule Nov 13 '16 at 15:19

2 Answers2

7

Ok, I found a way to do what I want using a 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")
    })

}
Etienne Beaule
  • 207
  • 2
  • 10
0

You have to use DownloadRequest and use progress.

Also take a look at this post, it explained:

Alamofire POST request with progress

Community
  • 1
  • 1
thierryb
  • 3,660
  • 4
  • 42
  • 58
  • Humm... I don't want to show a download progress. I want to update the hud at the beginning of each new request to let the user know what is being downloaded. The individual request are quite fast, it's the html parsing that can take a while... – Etienne Beaule Nov 12 '16 at 11:51