12

As you can see, I'm receiving a JSON file, parsing it using SwiftyJSON, and trying to return totalTime, but it won't let me. How do I do this?

func googleDuration(origin: String, destination: String) -> Int{
    // do calculations origin and destiantion with google distance matrix api

    let originFix = origin.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);
    let destinationFix = destination.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);

    let urlAsString = "https://maps.googleapis.com/maps/api/distancematrix/json?origins="+originFix+"&destinations="+destinationFix;
    println(urlAsString);

    let url = NSURL(string: urlAsString)!
    let urlSession = NSURLSession.sharedSession()

    let task = urlSession.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
        if error != nil {
            // If there is an error in the web request, print it to the console
            println(error.localizedDescription)
        }

        println("parsing JSON");
        let json = JSON(data: data);
        if (json["status"].stringValue == "OK") {
            if let totalTime = json["rows"][0]["elements"][0]["duration"]["value"].integerValue {
                println(totalTime);
            }
        }
    })
    task.resume();
}
teo751
  • 321
  • 1
  • 4
  • 13
  • btw, rather than just replacing spaces with `+`, you might consider doing a proper percent escape, as outlined here http://stackoverflow.com/a/24888789/1271826 – Rob Nov 23 '14 at 11:59

3 Answers3

12

You should add your own completionHandler closure parameter and call it when the task completes:

func googleDuration(origin: String, destination: String, completionHandler: (Int?, NSError?) -> Void ) -> NSURLSessionTask {
    // do calculations origin and destiantion with google distance matrix api

    let originFix = origin.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);
    let destinationFix = destination.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);

    let urlAsString = "https://maps.googleapis.com/maps/api/distancematrix/json?origins="+originFix+"&destinations="+destinationFix
    println(urlAsString)

    let url = NSURL(string: urlAsString)!
    let urlSession = NSURLSession.sharedSession()

    let task = urlSession.dataTaskWithURL(url) { data, response, error -> Void in
        if error != nil {
            // If there is an error in the web request, print it to the console
            // println(error.localizedDescription)
            completionHandler(nil, error)
            return
        }

        //println("parsing JSON");
        let json = JSON(data: data)
        if (json["status"].stringValue == "OK") {
            if let totalTime = json["rows"][0]["elements"][0]["duration"]["value"].integerValue {
                // println(totalTime);
                completionHandler(totalTime, nil)
                return
            }
            let totalTimeError = NSError(domain: kAppDomain, code: kTotalTimeError, userInfo: nil) // populate this any way you prefer
            completionHandler(nil, totalTimeError)
        }
        let jsonError = NSError(domain: kAppDomain, code: kJsonError, userInfo: nil) // again, populate this as you prefer
        completionHandler(nil, jsonError)
    }
    task.resume()
    return task
}

I'd also have this return the NSURLSessionTask in case the caller wants to be able to cancel the task.

Anyway, you'd call this like so:

googleDuration(origin, destination: destination) { totalTime, error in
    if let totalTime = totalTime {
        // use totalTime here
    } else {
        // handle error     
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Is there any way I could make this non-asynchronous? I need it to wait until there is a response parsed but I don't know how to do it. Your solution was right, I think my question was just wrong – teo751 Nov 22 '14 at 21:50
  • You _can,_ but it's seriously frowned upon. It's a horrible UX and if you do it at the wrong time, the watchdog process might summarily kill your app. The correct solution is to disable the UI, present spinner (in iOS, a `UIActivityIndicatorView`), and reverse that process when the request is done. You really should embrace the asynchronous pattern rather than fighting it. – Rob Nov 22 '14 at 22:02
  • By the way, often people will dispatch those completion handlers to the main queue (because the caller is usually engaging in UI updates). If you want me to update answer to show you how to do that, let me know. It's probably self explanatory if you're proficient with GCD, but I'm happy to show you, if you're not. – Rob Nov 22 '14 at 22:04
  • If I do this, it sets timeLabel as -999,not what I want.`var tripTime = -999; googleDuration(startLabel.text, destination: endLabel.text) { totalTime, error in if let totalTime = totalTime { // use totalTime here tripTime = totalTime; println(tripTime); } } println("Returning: "); println(tripTime); timeLabel.text = String(tripTime);` – teo751 Nov 22 '14 at 22:13
  • Move the setting of the `timeLabel.text` _inside_ the closure rather than after it. (But like I said, dispatch that to the main queue.) – Rob Nov 22 '14 at 22:26
  • it says it requires explicit 'self.', but when I do that, it doesn't assign the value – teo751 Nov 22 '14 at 22:28
  • 1
    Yep, so it's `self.timerLabel.text = ...`. Did you dispatch this to the main queue? – Rob Nov 22 '14 at 22:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/65451/discussion-between-teo751-and-rob). – teo751 Nov 22 '14 at 22:36
2

Another example:

   class func getExchangeRate(#baseCurrency: String, foreignCurrency:String, completion: ((result:Double?) -> Void)!){
    let baseURL = kAPIEndPoint
    let query = String(baseCurrency)+"_"+String(foreignCurrency)

    var finalExchangeRate = 0.0
    if let url = NSURL(string: baseURL + query) {
        NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in

            if ((data) != nil) {
                let jsonDictionary:NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: nil) as NSDictionary

                if let results = jsonDictionary["results"] as? NSDictionary{
                    if let queryResults = results[query] as? NSDictionary{
                        if let exchangeRate = queryResults["val"] as? Double{
                            let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
                            dispatch_async(dispatch_get_global_queue(priority, 0)) {
                                dispatch_async(dispatch_get_main_queue()) {
                                    completion(result: exchangeRate)
                                }
                            }

                        }
                    }
                }
            }
            else {
                completion(result: nil)
            }

        }.resume()
    }
}

Call:

 Currency.getExchangeRate(baseCurrency: "USD", foreignCurrency: "EUR") { (result) -> Void in
        if let exchangeValue = result {
            print(exchangeValue)
        }
    }
ericgu
  • 2,229
  • 23
  • 25
0

Another example:

func getJason(url: NSURL, completionHandler: (String?, NSError?) -> Void ) -> NSURLSessionTask {

    var finalData: String!
    let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in

        if error != nil{

            completionHandler(nil, error)
            return
        }
        else{

        if let urlContent = data{

            do{
                let jsonData = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers)

                if let ip = jsonData["ip"]{

                    finalData = ip as? String
                    completionHandler(finalData, nil)
                    return
                }

            }catch{
                print("EMPTY")
            }

        }

    }
}
    task.resume()
    return task
}

Then i called it in the viewDidLoad

getJason(url) { (ipAddress, error) -> Void in

        if error != nil{

            print(error)
        }
        else{
            if let ip = ipAddress{          //To get rid of optional

        self.ipLabelDisplay.text = "Your Ip Address is: \(ip)"

            }

        }
    }
Kegham K.
  • 1,589
  • 22
  • 40