Передача объекта из URLSession в ViewController

Я пытаюсь передать созданный мной объект под названием detail из быстрого файла с именем RestManager.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.


}

..

}

person William    schedule 24.06.2017    source источник


Ответы (3)


Используйте функцию close для передачи таких данных

'

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

'

person Irshad Ahmad    schedule 25.06.2017

Вы правы в том, что ваша проблема связана с попыткой немедленно прочитать ответ (поскольку маловероятно, что он будет готов немедленно). Вам нужен способ уведомить ваш контроллер (в основном потоке/очереди!), когда ваши данные готовы (или произошла ошибка).

Простым, но неэлегантным решением было бы добавить weak var controller: DetailViewController к вашему RestManager (слабому, поэтому он не вызывает цикл сохранения), который передается init ( RestManager(controller: self) ), и использовать эту ссылку в закрытии задачи URL, чтобы сообщить контроллеру, что он готов или ошибочен. вне.

Вероятно, вам следует использовать уведомления или шаблон делегата, чтобы избежать жесткой связи RestManager с DetailViewController.

person Joshua Nozzi    schedule 24.06.2017
comment
Вы можете использовать процесс делегирования, чтобы получить обратный вызов с вашим массивом деталей в качестве параметра в вашем контроллере представления. Теперь посмотрим, как реализовать делегат с протоколом в swift. - person Tanvir Nayem; 25.06.2017

Привет, я думаю, вы только что столкнулись с типичной ловушкой асинхронного программирования. Что вы делаете, так это запрашиваете возвращаемое значение задачи до того, как оно вернется из URLSession. Вы можете решить эту проблему, создав обработчик завершения самостоятельно, как в этом примере, где я получаю данные о погоде от Darksky. Обратите внимание, что для этого вам даже не нужно создавать класс, просто функцию.

PS обратите внимание, что я использую Alamofire, SwiftyJSON и Gloss, что значительно упрощает использование интерфейсов REST. И это Свифт 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)
            }
        }

Обратите внимание, что я заполняю текстовые поля только после завершения обработчика завершения и фактически возвращает либо некоторые данные, либо ошибку. Не обращайте внимания на материал «effectView», который предназначен для специального счетчика ожидания курсора :-)

А также может быть полезно знать эти структуры, которые сопоставляются с данными 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
    }
}
person Lars Christoffersen    schedule 25.06.2017