Swift:如何在异步 urlsession 函数中返回一个值?

Posted

技术标签:

【中文标题】Swift:如何在异步 urlsession 函数中返回一个值?【英文标题】:Swift: How do I return a value within an asynchronous urlsession function? 【发布时间】:2014-11-22 18:42:24 【问题描述】:

如您所见,我正在接收一个 JSON 文件,使用 SwiftyJSON 对其进行解析,并尝试返回 totalTime,但它不会让我这样做。我该怎么做?

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();

【问题讨论】:

顺便说一句,不只是用+ 替换空格,您可以考虑进行适当的百分比转义,如此处所述***.com/a/24888789/1271826 【参考方案1】:

您应该添加自己的completionHandler 闭包参数并在任务完成时调用它:

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

如果调用者希望能够取消任务,我也会返回 NSURLSessionTask

不管怎样,你会这样称呼它:

googleDuration(origin, destination: destination)  totalTime, error in
    if let totalTime = totalTime 
        // use totalTime here
     else 
        // handle error     
    

【讨论】:

有什么办法可以使这个非异步的?我需要它等到解析出响应,但我不知道该怎么做。您的解决方案是对的,我认为我的问题是错误的 可以,但它严重不受欢迎。这是一个可怕的用户体验,如果你在错误的时间这样做,看门狗进程可能会立即杀死你的应用程序。正确的解决方案是禁用 UI,显示微调器(在 ios 中,UIActivityIndicatorView),并在请求完成时反转该过程。你真的应该接受异步模式而不是反对它。 顺便说一句,人们通常会将这些完成处理程序分派到主队列(因为调用者通常会进行 UI 更新)。如果您希望我更新答案以向您展示如何做到这一点,请告诉我。如果您精通 GCD,这可能是不言自明的,但如果您不精通,我很乐意向您展示。 如果我这样做,它会将 timeLabel 设置为 -999,而不是我想要的。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); 是的,它是self.timerLabel.text = ...。你把它分派到主队列了吗?【参考方案2】:

另一个例子:

   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()
    

呼叫:

 Currency.getExchangeRate(baseCurrency: "USD", foreignCurrency: "EUR")  (result) -> Void in
        if let exchangeValue = result 
            print(exchangeValue)
        
    

【讨论】:

【参考方案3】:

另一个例子:

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

然后我在 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)"

            

        
    

【讨论】:

以上是关于Swift:如何在异步 urlsession 函数中返回一个值?的主要内容,如果未能解决你的问题,请参考以下文章

如何在从 Swift 3 中的函数返回结果之前等待 URLSession 完成

swift--使用URLSession异步加载图片

从 URLSession 完成处理函数 Swift 3 返回字符串

Swift,URLSession,viewDidLoad,tableViewController

如何在 Swift 中使用 URLSession 缓存图像

Swift UrlSession 在 UrlSession 中不起作用