0

I'm trying to learn a bit about making API calls in SwiftUI. I have a function called loadData which runs via the onAppear modifier.

The goal of that function is to see if I currently have data in CoreData.
If there is no data in CoreData, then I'd like to call another function that makes the API call to get the data, but only return the fetched data.

With the example I have below, the getCurrentSol function returns before the async portion is finished. Resulting in no data being returned. What is the appropriate way for me to return the data?

As you can see, I did try a while(true) "trick". But for whatever reason, my results variable never even updates with the fetched data, even though the decodedData variable does contain the proper results.

}.onAppear(perform: loadData)
}

func loadData() {
    print("data: \(storedData) ")
    print("data.count: \(storedData.count)")
    
    if(storedData.count == 0){
        let fetchedData = getCurrentSol()
        let currentSol = fetchedData.sol
        print("fetchedData: \(fetchedData)")
        print("currentSol: \(currentSol)")

    }
    
}

func getCurrentSol() -> CuriosityRoverModel {
    var results =  CuriosityRoverModel(sol: 0, low: 0, high: 0, opacity: "Sunny", sunrise: "00:00", sunset: "00:00", month: "Month 0")
    let urlString = "https://api.maas2.apollorion.com"
    let url = URL(string: urlString)
    URLSession.shared.dataTask(with: url!) {data, response, error in
        DispatchQueue.main.async {
            if let data = data {
                do {
                    let decoder = JSONDecoder()
                    let decodedData = try decoder.decode(CuriosityRoverModel.self, from: data)
                    //This recieves the proper data, but it doesn't get written to the results var
                    print("decodedData: \(decodedData)")
                    results = decodedData
                } catch {
                    print("Error: ", error)
                }
            }
        }
    }.resume()
    // I thought this would be a way to wait for the data
    // but results never gets updated so it ends up running endlessly
    while(true){
        if(results.sol > 0){
            return results
        }
    }
    //return results // This would just return the "empty" results var from above before the data is actually retrieved
}

}

Nate Thompson
  • 325
  • 2
  • 12
  • You need to use a completion handler by using `@escaping` or by doing a non escaping completion handler. Check out this article for more info https://medium.com/@dhavalkansara51/completion-handler-in-swift-with-escaping-and-nonescaping-closures-1ea717dc93a4. – Trev347 Dec 25 '21 at 07:04
  • @Trev347 is right. But if you want to avoid introducing a lot of completion handlers you could use the async/await equivalent of URLSession's `dataTask`. https://wwdcbysundell.com/2021/using-async-await-with-urlsession/ – atultw Dec 25 '21 at 22:27

1 Answers1

2

There are many ways to achieve what you want. This is one approach, using a closure:

    ....
       .onAppear(perform: loadData)
}

func loadData() {
    print("data: \(storedData) ")
    print("data.count: \(storedData.count)")
    
    if (storedData.count == 0) {
        getCurrentSol() { results in    // <--- here
            if let fetchedData = results {
                let currentSol = fetchedData.sol
                print("fetchedData: \(fetchedData)")
                print("currentSol: \(currentSol)")
            }
        }
    }
}

// use a completion closure to "return" your results when done, not before
func getCurrentSol(completion: @escaping (CuriosityRoverModel?) -> Void) {
    let urlString = "https://api.maas2.apollorion.com"
    let url = URL(string: urlString)
    URLSession.shared.dataTask(with: url!) {data, response, error in
        DispatchQueue.main.async {
            if let data = data {
                do {
                    let decoder = JSONDecoder()
                    let decodedData = try decoder.decode(CuriosityRoverModel.self, from: data)
                    print("decodedData: \(decodedData)")
                    completion(decodedData)  // <--- here, return the results
                } catch {
                    print("Error: ", error) // need to deal with errors
                    completion(nil)   // <--- here, should return the error
                }
            }
        }
    }.resume()
}
}