Search code examples
iosswiftalamofire

How do I prioritise my Alamofire requests so that they execute before the function returns?


So I have a function that returns a list of topics which is supposed to be returned via an api, but the response closure only executes after the functions returns.

My Code:

    func getSectionsList (syllabus_ID: String) -> [String:String] {

        var sectionDictionary = [String:String]()
        var errorString: String!
        let token = Keychain.value(forKey: "Auth_Token")! as String
        manager.request(.GET, "\(BASE_URL)/syllabi/\(syllabus_ID)/sections?token=\(token)", encoding:.JSON).validate()
            .responseJSON { response in
                switch response.result {
                case .Success:

                    let responseJSON = JSON(response.result.value!)
                    sectionDictionary = self.serializeJSON(responseJSON)

                    break
                case .Failure(let error):
                    NSLog("Error result: \(error)")
                    errorString = "\(error)"              
                    return
                }
            }
        return sectionDictionary
    }

I tried using completion handlers like this:

func getSectionsList (syllabus_ID: String, completionHandler: ([String:String], String?)->()) {

    var sectionDictionary = [String:String]()
    var errorString: String!
    let token = Keychain.value(forKey: "Auth_Token")! as String
    manager.request(.GET, "\(BASE_URL)/syllabi/\(syllabus_ID)/sections?token=\(token)", encoding:.JSON).validate()
        .responseJSON { response in
            switch response.result {
            case .Success:

                let responseJSON = JSON(response.result.value!)
                sectionDictionary = self.serializeJSON(responseJSON)

                break
            case .Failure(let error):
                NSLog("Error result: \(error)")
                errorString = "\(error)"
               return
            }
             completionHandler(sectionDictionary, errorString)
    }
}

And called it like this:

var dictionary = [String:String]()

    api.getSectionsList("1"){
        (dict, error)in
        if dict.count != 0{
         dictionary = dict
        }
 }

So now it does return the value, but it feels like there must surely be a more simple way? Is it not possible to have a function that just returns the values I want without having to achieve it like I have done?


Solution

  • Nope, you've hit the nail on the head with the closure (completion handler) approach.

    The Alamofire function .responseJSON() is coming back to you asynchronously which you also have to return asynchronously through the closure completion handler pattern.

    Any returns inside a closure are returns for the closure itself, not the scope surrounding the closure.

    You've defined your closure type as,

    ([String:String], String?)->()
    

    AKA, to better illustrate my point, is the same as this,

    [String:String], String?)->Void
    

    That means your closure doesn't want a return value anyway (hence the Void) and I'd expect the compiler to complain if you tried to do so.

    You'll get use to this async closure pattern the more you use it.

    If you truly want a return value from your function, I think you might be able to do it with Alamofire using synchronous requests although I've never done it and I don't see a reason to lock up execution cycles why you wait for a relatively slow task to complete.

    One other thing to be careful with is what thread your closure is originally getting called on. I believe .responseJSON() will always be returned on a background thread if I'm not mistaken. So, say you want to update your UI after that request returns, if its not on the main thread you will eventually run into issues. A quick test will tell you,

    if NSThread.mainThread() == NSThread.currentThread() {
        print("ON MAIN THREAD")
    } else {
        print("NOT ON MAIN THREAD")
    }
    

    To make sure its on the main do something like this with GCD,

    dispatch_async(dispatch_get_main_queue(),{
         completionHandler(sectionDictionary, errorString)
    }