无法从Swift 3的dataTask中获取JSON数据

问题描述:

第一篇文章,请温和。我在Swift 3,Xcode 8.1中玩弄了JSON数据。我特别从http://forecast.weather.gov/MapClick.php?lat=38.9782&lon=-76.4933&FcstType=json获取我的数据。我的代码如下:无法从Swift 3的dataTask中获取JSON数据

private func getWeatherData(url:URL) -> (currentObservation:Dictionary<String, AnyObject>, currentData:Dictionary<String, AnyObject>) { 
    var currentObservation:Dictionary = [:] as Dictionary<String, AnyObject> 
    var currentData:Dictionary = [:] as Dictionary<String, AnyObject> 
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in 
     if error != nil { 
      print ("Error retrieving URLSession") 
     } 
     else 
     { 
      if let content = data 
      { 
       do 
       { 
        let JSONData = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject 

        if let observation = JSONData["currentobservation"] as? Dictionary<String, AnyObject> { 
         currentObservation = observation 
         print("currentObservation inside of task: \(currentObservation)\n\n") 
        } 

        if let data = JSONData["data"] as? Dictionary<String, AnyObject> { 
         currentData = data 
         print("currentData inside of task: \(currentData)") 
        } 

       } 
       catch 
       { 
        print("Error in trying JSONSerialization") 

       } 

      } 
     } 
    } 

    task.resume() 

    print("currentObservation outside of task: \(currentObservation) \n\n") 
    print("currentData outside of task: \(currentData)") 

    return (currentObservation , currentData) 

} 

我得到的输出是:

currentObservation outside of task: [:] 


currentData outside of task: [:] 
currentObservation: [:] 


currentData: [:] 
currentObservation inside of task: ["state": MD, "name": Annapolis,  United States Naval Academy, "longitude": -76.49, "elev": 3, "Relh": 79, "WindChill": NA, "Weather": Fair, "Altimeter": 1028.7, "latitude": 38.99, "Temp": 46, "SLP": 30.37, "id": KNAK, "timezone": EST, "Weatherimage": nsct.png, "Windd": 0, "Winds": 0, "Visibility": 10.00, "Gust": 0, "Dewp": 40, "Date": 7 Nov 20:54 pm EST] 


currentData inside of task: ["hazard": <__NSArrayM 0x60800005a520>(
Frost Advisory, Hazardous Weather Outlook), "weather": <__NSArrayM 0x60800005a490>(
Clear then Areas Frost, Areas Frost then Sunny, 
Partly Cloudy then Slight Chance Showers, etc.(edited for brevity)] 

我得到一个JSON数据的反应,但我似乎无法保持数据的保持。

我很困惑的几件事情。首先,为什么URLSession.shared.dataTask之外的打印语句在打印语句之前打印?

此外,“currentObservation”和“currentData”的打印语句在函数外调用,并且它们打印在URLSession.shared.dataTask中的打印语句之前。这是为什么?

最后,最重要的是,为什么JSON数据没有超出URLSession.shared.dataTask块呢?

任何洞察将有所帮助。谢谢。

编辑: 这是我现在的代码基于维诺德的回应。当我打印它们时,currentObservation和currentData仍然是空的。

class ViewController: UIViewController { 

override func viewDidLoad() { 
    super.viewDidLoad() 
    var currentObservation:Dictionary = [:] as Dictionary<String, AnyObject> 
    var currentData:Dictionary = [:] as Dictionary<String, AnyObject> 

    let serverURL = URL(string:"http://forecast.weather.gov/MapClick.php?lat=38.9782&lon=-76.4933&FcstType=json") 
    getWeatherData(serverURL: serverURL!, completion: { serverResponse in 
     if let observation = serverResponse["currentobservation"] as? Dictionary<String, AnyObject> { 
      currentObservation = observation 
      print("currentObservation inside of task: \(currentObservation)\n\n") 
     } 

     if let data = serverResponse["data"] as? Dictionary<String, AnyObject> { 
      currentData = data 
      print("currentData inside of task: \(currentData)\n\n") 
     } 
    }) 

    print("currentObservation outside of task: \(currentObservation)\n\n") 
    print("currentData outside of task: \(currentData)") 



} 

override func didReceiveMemoryWarning() { 
    super.didReceiveMemoryWarning() 
    // Dispose of any resources that can be recreated. 
} 


private func getWeatherData(serverURL:URL, completion:@escaping (AnyObject) ->()){ 
    let task = URLSession.shared.dataTask(with: serverURL) { (data, response, error) in 
     if error != nil { 
      print ("Error retrieving URLSession") 
     } 
     else 
     { 
      if let content = data 
      { 
       do 
       { 
        let JSONData = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject 

        completion(JSONData) 

       } 
       catch 
       { 
        print("Error in trying JSONSerialization") 

       } 

      } 
     } 
    } 

    task.resume() 


} 

}

是的,我知道我不应该在所有的视图控制器违反了MVC的可以这样做,但我只是玩弄的努力得到它添加的复杂工作之前数据模型和控制器以及UI。谢谢。

+0

'dataTask'以异步方式工作。您不能从包含异步任务的方法返回任何内容。 – vadian

+0

我想这是有道理的。如果在函数调用完成时已完成采样,那么如何返回任何内容。谢谢。 – Yrb

+0

Vinod的答案提供了一种使用回调闭包异步返回数据的解决方案。此外,在这里有几百个相关的主题。 – vadian

首先,URLSession.shared.dataTask闭包内的代码实际上并未在主线程中执行,一旦您调用task.resume()它将会被调用,并且它将在后台运行,因此您在其外部执行的所有打印都将首先被调用,因为这task还没有完成,这就是为什么打印currentObservation inside of task里面会后

二被调用,你不*使用任务的响应代码块的,试想这样的:创建任务 - >恢复任务 - >网络请求.... - >完成代码关闭,您必须执行代码,包含完成代码关闭中的响应,这是持有打印的部分currentObservation inside of task

+0

感谢您的解释。我认为后台线程必须专门调用。我始终打算最终在后台提出请求。当然,这将解释时间。这当然会导致如何在封闭之外使用响应的问题。 – Yrb

+0

你不这样做,当然你可以保存它作为属性和调用方法或任何东西,但都必须从内部开始结束关闭,因为那是你得到的回应,而不是从其他地方 – Tj3n

你的关闭不好。请改变如下,以获得适当的回应。

服务呼叫方法:

private func getWeatherData(serverURL:URL ,completion:@escaping (AnyObject) ->()){ 
      let task = URLSession.shared.dataTask(with: serverURL) { (data, response, error) in 
       if error != nil { 
        print ("Error retrieving URLSession") 
       } 
       else 
       { 
        if let content = data 
        { 
         do 
         { 
          let JSONData = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject 

          completion(JSONData)   

         } 
         catch 
         { 
          print("Error in trying JSONSerialization") 

         } 

        } 
       } 
      } 

      task.resume() 


     } 

响应解析:

var currentObservation:Dictionary = [:] as Dictionary<String, AnyObject> 
     var currentData:Dictionary = [:] as Dictionary<String, AnyObject> 

     let serverURL = URL(string:"http://forecast.weather.gov/MapClick.php?lat=38.9782&lon=-76.4933&FcstType=json") 
     getWeatherData(serverURL: serverURL!, completion: { serverResponse in 
      if let observation = serverResponse["currentobservation"] as? Dictionary<String, AnyObject> { 
       currentObservation = observation 
       print("currentObservation inside of task: \(currentObservation)\n\n") 
      } 

      if let data = serverResponse["data"] as? Dictionary<String, AnyObject> { 
       currentData = data 
       print("currentData inside of task: \(currentData)") 
      } 
     }) 
+0

因为我很明显关闭不够强大,我不得不查看逃逸关闭。这显然比尝试将信息作为返回值传递回来要优雅得多。我没有得到,仅仅为了我自己的知识,就是为什么将它作为回报值传递不起作用。你能解释一下吗?谢谢。 – Yrb

+0

我尝试了代码,但它仍然无法正常工作。我怀疑这个问题是因为封闭性质作为后台线程。当我查看变量时,闭包没有逃脱,因此它们仍然是空的。所以,我的问题是如何对它进行编码,以便在查看它们之前等待变量填充完成。我将发布我的整个代码,但响应数据响应仍然相同。 – Yrb

+0

@Yrb它没有返回值的原因是因为在返回行被命中时该值不存在。 'dataTask'是一个异步任务。也就是说,它将在不同线程的后台运行。所以发生的事情的顺序是...... 1.开始任务。 2.到达函数的结尾并返回你的值(它仍然是'[:]')3.任务完成并进入完成关闭。 4.解析JSON。 5.赋值给'currentObservation'和'currentData'。正如你在这里看到的。事情发生的顺序并不是代码中写入行的顺序。 – Fogmeister