将对象从 URLSession 传递到 ViewController

Passing object from URLSession to ViewController

我正在尝试将我创建的名为 detail 的对象从名为 RestManager.swift 的 swift 文件传递​​到 ViewController。该对象包含所有元素,但是当我在我的视图控制器中调用它时,它是空的。从我在网上收集到的信息来看,它可能与在后台线程上工作的 URLSession 有关

我的 RestManager.swift 看起来像这样。

class RestManager {


func reqDetails(id: Int) {
    // Create URL
    let id = String(id)
    let url = "https://website.example.com/"
    let url = URL(string: url + id)!

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in

        if error != nil
        {
            print ("ERROR")
        }

        else
        {
            if let content = data
            {

                    let jsonData = JSON(data: content)

                    let id = jsonData["id"].intValue
                    let name = jsonData["title"]["rendered"].string!
                    let link = jsonData["link"].url!
                    let content = jsonData["content"]["rendered"].string!


                    // Create Object
                    let detail = Detail(id: id, name: name, content: content, thumbnailUrl: link)
                    self.details.append(detail)

            }
        }
    }
    task.resume()
}
}

我的视图控制器看起来像这样:

class DetailViewController: UIViewController {

var ListingID = Int()
let restManager = RestManager()

@IBOutlet weak var ContentLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()
    restManager.reqDetails(id: ListingID)
    ContentLabel.text? = restManager.details[0].name // After running the app this index value is out of range.


}

..

}

您是对的,您的问题是由于尝试立即读取响应(因为它不太可能立即准备好)。当您的数据准备就绪(或发生错误)时,您需要一种方法来通知您的控制器(在主要 thread/queue!)。

一个简单但不优雅的解决方案是将 weak var controller: DetailViewController 添加到传递给 initRestManager(较弱,因此不会导致保留周期)(RestManager(controller: self) ) 并在 URL 任务闭包中使用该引用来告诉控制器它已经准备好或出错了。

您可能应该使用通知或委托模式来避免 RestManagerDetailViewController 的紧密耦合。

在函数中使用closer来像这样传递数据

'

func reqDetails(id: Int,completionHandler:@escaping (_ detilObject:Detail)->Void) {
    // Create URL
    let id = String(id)
    let url = "https://website.example.com/"
    let url = URL(string: url + id)!

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in

        if error != nil
        {
            print ("ERROR")
        }

        else
        {
            if let content = data
            {

                    let jsonData = JSON(data: content)

                    let id = jsonData["id"].intValue
                    let name = jsonData["title"]["rendered"].string!
                    let link = jsonData["link"].url!
                    let content = jsonData["content"]["rendered"].string!


                    // Create Object
                    let detail = Detail(id: id, name: name, content: content, thumbnailUrl: link)
                    self.details.append(detail)
                    completionHandler(self.details)

            }
        }
    }
    task.resume()
}

'

并像这样调用您的函数。 '

restManager.reqDetails(id: ListingID , completionHandler: { (detail) in
                // here is your detail object
            })

'

您好,我认为您刚刚遇到了典型的异步编程陷阱。您所做的是在任务从 URLSession 返回之前请求任务的 return 值。 你可以通过自己制作一个完成处理程序来解决它,就像这个例子一样,我从 Darksky 获得了一些天气数据。 请注意,您甚至不需要为此创建一个 class,只需一个函数即可。

PS 注意我使用 Alamofire、SwiftyJSON 和 Gloss,这大大降低了使用 REST 接口的复杂性。这是 Swift 3!

import Alamofire
import SwiftyJSON
import Gloss


typealias DarkSkyWeatherForecast = ( _ json : Gloss.JSON?,  _ error : Error?) -> Void


func getWeatherForcast( latitude:Double, longitude:Double,  completionHandler:@escaping DarkSkyWeatherForecast) -> Void {

    let urlString =  "https://api.darksky.net/forecast/"+darkSkyKey+"/"+String(latitude)+","+String(longitude)+"?units=si"

    Alamofire.request(urlString).responseJSON { (response) in
        if let resp = response.result.value {
            let json = JSON(resp)
            let glossJson = json.dictionaryObject
            completionHandler( glossJson, nil)
        }else{
            completionHandler( nil, response.error)
        }
    }
}

然后像这样调用函数:

getWeatherForcast(latitude: lat, longitude: lon) { (jsonArg, error) in
            if error == nil{
                guard let weather = DarkSkyWeather(json: jsonArg!) else {
                    self.effectView.removeFromSuperview()
                    return
                }
                if let ambTemp = weather.currently?.temperature, let windspd = weather.currently?.windSpeed, let windDir = weather.currently?.windBearing{
                    self.effectView.removeFromSuperview()
                    self.ambTemperature.text = String(ambTemp)
                    self.windSpeed.text = String( windspd )
                    self.windDirection.text = String( windDir )
                    self.getSpeed()
                }
            }else{

               self.effectView.removeFromSuperview()
                let alert = UIAlertController(title: "Error", message: "Could not get weather forecast", preferredStyle: .alert)
                let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
                    self.dismiss(animated: true, completion: nil)
                })
                alert.addAction(okAction)
                self.present(alert, animated: true, completion: nil)
            }
        }

注意,我只在完成处理程序完成后才填充文本字段,实际上 returns 一些数据或错误。忽略 "effectView" 的东西,那是一个特殊的 curser wait spinner :-)

并且了解映射到 json 数据的这些结构可能会有所帮助:

//common struct for weather data
public struct WeatherDataStruct : Decodable{
    let time : Int?
    let summary : String?
    let icon : String?
    let precipIntensity : Double?
    let precipProbability : Double?
    let precipType : String?
    let temperature : Double?
    let apparentTemperature : Double?
    let dewPoint : Double?
    let humidity: Double?
    let windSpeed : Double?
    let windBearing : Int?
    let visibility : Double?
    let cloudCover : Double?
    let pressure : Double?
    let ozone : Double?

    public init?( json: JSON){
        self.time = "time" <~~ json
        self.summary = "summary" <~~ json
        self.icon = "icon" <~~ json
        self.precipIntensity = "precipIntensity" <~~ json
        self.precipProbability = "precipProbability" <~~ json
        self.precipType = "precipType" <~~ json
        self.temperature = "temperature" <~~ json
        self.apparentTemperature = "apparantTemperature" <~~ json
        self.dewPoint = "dewPoint" <~~ json
        self.humidity = "humidity" <~~ json
        self.windSpeed = "windSpeed" <~~ json
        self.windBearing = "windBearing" <~~ json
        self.visibility = "visibility" <~~ json
        self.cloudCover = "cloudCover" <~~ json
        self.pressure = "pressure" <~~ json
        self.ozone = "ozone" <~~ json
    }
}

//hourly weather struct
public struct HourlyStruct : Decodable{
    let summary : String?
    let icon : String?
    let data : [WeatherDataStruct]?

    public init?(json: JSON) {
        self.summary = "summary" <~~ json
        self.icon = "icon" <~~ json
        self.data = "data" <~~ json
    }
}

//total struct for the whole json answer from darksky weather
public struct DarkSkyWeather : Decodable{
    let latitude : Double?
    let longitude : Double?
    let timezone : String?
    let offset : Int?
    let currently : WeatherDataStruct?
    let hourly : HourlyStruct?

    public init?(json: JSON) {
        self.latitude = "latitude" <~~ json
        self.longitude = "longitude" <~~ json
        self.timezone = "timezone" <~~ json
        self.offset = "offset" <~~ json
        self.currently = "currently" <~~ json
        self.hourly = "hourly" <~~ json
    }
}