Swift || Возвращает класс, который может использоваться другими методами из вызова API

#ios #swift #asynchronous #completionhandler

#iOS #swift #асинхронный #завершениеhandler

Вопрос:

Я вызываю HERE Weather API из мобильного приложения, и мне нужно вернуть текущую погоду в виде объекта, чтобы другие методы могли использовать эту информацию в течение заданного периода времени (например, 30-минутные интервалы обновления).

У меня есть приблизительное представление об асинхронных вызовах на основе этого сайта https://fluffy.es/return-value-from-a-closure / но я все еще сталкиваюсь со своей проблемой, когда я не могу получить доступ к объекту погоды, который я создаю вне закрытия обработчика завершения.

Я предоставил метод Get из моего класса Weather. Я также предоставил вызов этого метода Get в файле AppDelegates .

 public static func Get(lat:String, long:String, completionHandler: @escaping (Weather?)  -> Void) {

    //Keys go here
    var request = URLRequest(
        url: URL(string: "https://weather.cit.api.here.com/weather/1.0/report.json?app_code=-fJW5To2WdHdwQyYr_9ExQamp;app_id=2m83HBDDcwAqTC2TqdLRamp;product=observationamp;latitude=" lat "amp;longitude=" long "amp;oneobservation=true")!)
    request.httpMethod = "GET"


    URLSession.shared.dataTask(with: request, completionHandler: { dat, response, error in
        do {

            guard let json = try? JSONSerialization.jsonObject(with: dat as! Data, options: []) else {throw JSONParseError.missingJSON}

            //parse json for dictionaries
            guard let response = json as? [String: Any] else {throw JSONParseError.responseNotADictionary}
            guard let observations = response["observations"] as? [String: Any] else {throw JSONParseError.observationsNotADictionary}
            guard let locationJSON = observations["location"] as? [Any] else {throw JSONParseError.locationNotAnArray}
            guard let location = locationJSON.first as? [String: Any] else {throw JSONParseError.locationNotADictionary}
            guard let observationJSON = location["observation"] as? [Any] else {throw JSONParseError.observationNotAnArray}
            guard let observation = observationJSON.first as? [String: Any] else {throw JSONParseError.observationNotADictionary}

            //search dictionaries for values
            guard let feedCreation = response["feedCreation"] as? String else {throw JSONParseError.missingFeedCreationObject}
            guard let city = location["city"] as? String else {throw JSONParseError.missingCityObject}
            guard let state = location["state"] as? String else {throw JSONParseError.missingStateObject}
            guard let temperature = observation["temperature"] as? String else {throw JSONParseError.missingTemperatureObject}
            guard let icon = observation["icon"] as? String else {throw JSONParseError.missingIconObject}
            guard let iconName = observation["iconName"] as? String else {throw JSONParseError.missingIconNameObject}

            //create weather object and return
            guard let currentWeather = Weather(feedCreation: feedCreation,state: state,city: city,temperature: temperature,icon: icon,iconName: iconName) else {throw WeatherObjectCreationError.objectCreationFailed}

            completionHandler(currentWeather)

        } catch {
            print("JSON Serialization error")
        }

    }).resume()
}
 
 Weather.Get(lat:"32.9005", long:"-96.7869", completionHandler: {currentWeather in
            if let testWeather = currentWeather{
//Works fine
print(testWeather.city)
                print("completionHandler")
            }
})
//Error says testWeather is not intialized 
print(testWeather.city)
 

В конце моей погоды.Получить вызов Я должен иметь возможность получить доступ к объекту testWeather из других методов. В частности, метод, который изменяет ограничение скорости на основе текущей погоды в области, которая возвращается погодой.Получить вызов.

Комментарии:

1. Вы сказали, чего не происходит, но было бы полезно узнать, что происходит. Это печать "JSON Serialization error" или "completionHandler" ? Если вы поместите точку останова в обработчик завершения dataTask, попадает ли она когда-нибудь?

2. Мои извинения, когда я печатаю testWeather.city внутри обработчика завершения, все печатается. Когда я пытаюсь напечатать вне обработчика завершения, он говорит, что testWeather не инициализирован. Насколько я понимаю, это связано с порядком выполнения кода (т.е. print(testWeather.city) выполняется до выполнения обработчика завершения)

3. Это правильно, вы можете использовать только testWeather внутри замыкания, но вы можете передать его другому методу или сохранить в переменной, чтобы другие части вашего кода могли получить к нему доступ.

4. testWeather является локальным для используемого вами замыкания. К нему нельзя получить доступ за пределами этого закрытия. Вы должны сохранить его в любом другом объекте погоды, к которому можно получить доступ позже, например, в свойстве.

Ответ №1:

Чего вы не поняли, так это того, что обработчик завершения в вашем вызове GET является таким же асинхронным, как и исходный вызов Weather API. Таким образом, вы не можете запускать после него какой-либо код, который зависит от того, что в нем содержится. Порядок событий следующий:

 // 1
Weather.Get(lat:"32.9005", long:"-96.7869", completionHandler: {currentWeather in
    if let testWeather = currentWeather{ // 3!!!
        print(testWeather.city) // 4!!!!!!
        print("completionHandler")
    }
})
print(testWeather.city) // 2
 

Более того, что находится testWeather в последней строке? Является ли это свойством, self.testWeather ? Это должно быть. Но даже если это так, вы забыли указать self.testWeather значение; if let testWeather это другое testWeather (оно локально только для обработчика завершения и не может «просочиться» из него). Однако, даже если бы вы это сделали, это все равно не сработало бы, потому что код все равно выполнялся бы в неправильном порядке:

 // 1
Weather.Get(lat:"32.9005", long:"-96.7869", completionHandler: {currentWeather in
    if let testWeather = currentWeather{ // 3
        print(testWeather.city) // 4
        print("completionHandler") 
        self.testWeather = testWeather // 5; better but it won't help the _next_ print
    }
})
print(testWeather.city) // 2
 

Тем не менее, если не забыть записать в self.testWeather (5), по крайней мере, другой код получит доступ к этому значению, при условии, что он будет запущен позже.

Комментарии:

1. Этот вопрос подробно обсуждается в моем сообщении в блоге programmingios.net /…

2. Спасибо, теперь я лучше понимаю выполнение кода. Означает ли это, что я должен перемещать любые другие вызовы, которые зависят от погодных данных внутри погоды. Получить comletionHandler?

3. По сути, абсолютно, да, если вы хотите, чтобы они вызывались в ответ на тот факт, что теперь у вас есть информация о погоде. Опять же, см. programmingios.net/what-asynchronous-means .