Swift-功能在任务完成之前就完成了

时间:2020-05-12 03:58:15

标签: swift multithreading asynchronous task

此代码的目的是,当您双击MapKit视图时,它将在地图上放置一个图钉并获取经度和纬度坐标。当它放下引脚时,我希望它调用API并让该API返回城市名称。我想通过在地图视图上放置标签来显示城市名称,只要您放置新的图钉,该标签就会更新。

当引脚掉线时,此功能称为:

func previewDataOnButton(){
    print("PreviewDataOnButton Called")
    theWeatherDataModel.callAPI(latitude: latitude!, longitude: longitude!)
    cityStateLabel.text = theWeatherDataModel.cityName
    print("PreviewDataOnButton Finished")
}

此函数在视图控制器中,它在单独的模型中调用一个函数。它正在调用的函数如下:

func callAPI(latitude: String, longitude: String){
    let baseURL = "https://api.weatherbit.io/v2.0/current?&lat=\(latitude)&lon=\(longitude)&key=\(apiKey)"
    let urlComponents = URLComponents(string: baseURL)
    let theURL = urlComponents?.url
            
    let session = URLSession(configuration: .ephemeral)
    let theTask = session.dataTask(with: theURL!) {
        (data, response, error) in
        if let actualError = error{
            print("We got an error")
        } else if let actualData = data, let actualResponse = response{
            print("We got stuff back")
            let parsedData = try? JSON(data: actualData)
            //print("Data: \n\(String(describing: parsedData))")
            
            if let theWeatherArray = parsedData?["data"]{
                for(_, singleWeatherDictionary) in theWeatherArray{
                    
                    self.cityName = singleWeatherDictionary["city_name"].string
                }
            }
            
        } else {
            print("How did I get here?")
        }
        print("Im done with the closure")           
    }
    print("About to start the data task")
    theTask.resume()
    print("Started data task")
    
}

我在整个代码中放置了打印语句以进行调试,并打印了以下内容:

调用了PreviewDataOnButton

关于开始数据任务

已启动数据任务

PreviewDataOnButton完成

我们回来了

我已经结束了

从输出来看,在模型中的函数可以完成其任务并调用模型之前,视图控制器中的函数似乎已完成。这会导致视图上的标签无法正确使用正确的城市名称进行更新,因为该函数在API实际获得城市名称之前便已完成。这是我遇到的问题,我们将不胜感激。

2 个答案:

答案 0 :(得分:1)

您应该等待api调用完成,然后更新UI。睡觉对你没有帮助。您可以将UI更新部分作为完成块(闭包)传递给api调用函数,并在调用完成后调用它。您最终可能会得到类似于以下的代码。

func previewDataOnButton(){
    print("PreviewDataOnButton Called")
    theWeatherDataModel.callAPI(latitude: latitude!, longitude: longitude!) { [weak self] completed in
        guard let self = self else {
            return
        }
        DispatchQueue.main.async {
            self.cityStateLabel.text = self.theWeatherDataModel.cityName
            print("PreviewDataOnButton Finished")
        }
    }
}
func callAPI(latitude: String, longitude: String, completionBlock: @escaping (Bool) -> Void){
    let baseURL = "https://api.weatherbit.io/v2.0/current?&lat=\(latitude)&lon=\(longitude)&key=\(apiKey)"
    let urlComponents = URLComponents(string: baseURL)
    let theURL = urlComponents?.url

    let session = URLSession(configuration: .ephemeral)
    let theTask = session.dataTask(with: theURL!) {
        (data, response, error) in
        if let actualError = error{
            print("We got an error")
            completionBlock(false)
        } else if let actualData = data, let actualResponse = response{
            print("We got stuff back")
            let parsedData = try? JSON(data: actualData)
            //print("Data: \n\(String(describing: parsedData))")

            if let theWeatherArray = parsedData?["data"]{
                for(_, singleWeatherDictionary) in theWeatherArray{

                    self.cityName = singleWeatherDictionary["city_name"].string
                }
            }
            completionBlock(true)

        } else {
            print("How did I get here?")
        }
        print("Im done with the closure")           
    }
    print("About to start the data task")
    theTask.resume()
    print("Started data task")

}

答案 1 :(得分:0)

您需要将完成处理程序添加到 callAPI 中,以等待功能结束。并且在函数中,您需要使用 DispatchGroup 来保持该函数,直到循环结束。 (您的WeatherWeatherArray的项目很少,这没问题。但是,当有大量项目时,它将发出问题)

func callAPI(latitude: String, longitude: String, completionHandler: @escaping(Result<Bool,Error>)->Void){
    let baseURL = "https://api.weatherbit.io/v2.0/current?&lat=\(latitude)&lon=\(longitude)&key=\(apiKey)"
    let urlComponents = URLComponents(string: baseURL)
    let theURL = urlComponents?.url

    let session = URLSession(configuration: .ephemeral)
    let theTask = session.dataTask(with: theURL!) {
        (data, response, error) in
        if let actualError = error{
            completionHandler(.failure(actualError))
        } else if let actualData = data, let actualResponse = response{

            let dGroup = DispatchGroup()
            let parsedData = try? JSON(data: actualData)

            if let theWeatherArray = parsedData?["data"]{
                for(_, singleWeatherDictionary) in theWeatherArray{
                    dGroup.enter()
                    self.cityName = singleWeatherDictionary["city_name"].string
                    dGroup.leave()
                }
            }

            dGroup.notify(queue: .main) {
                completionHandler(.success(true))
            }

        } else {
            completionHandler(.success(false))
        }
    }
    theTask.resume()
}

现在您可以像这样调用此函数

callAPI(latitude: "", longitude: "") { (response) in

        switch response{
        case .success(let granted):
            if granted{
                //success true
               cityStateLabel.text = theWeatherDataModel.cityName
            }else{
                //success false
            }
        case .failure(_):
            //error
        }
    }