I have a problem with my URLSession in Swift. No matter what I try (or how many tutorials I read through), I cannot find a way to get the network request to finish before executing the rest of my code. I was under the impression that a completion handler would do the trick, but this has not proven to be the case. What am I doing wrong here?
struct DataManager {
let eventsURL = URL(string: "https://spreadsheets.google.com/feeds/list/blablabla/1/public/full?alt=json")!
let decoder = JSONDecoder()
var data = Data()
func downloadEvents() -> [Event] {
var events: [Event] = []
var downloadedEvents: [[String: Any]] = []
getDataFromServer(forURL: eventsURL) { result in
downloadedEvents = result
}
// This next part always executes before the response is receive, which means the downloadedEvents variable is always empty.
for downloadedEvent in downloadedEvents {
if let event = Event(fromJSON: downloadedEvent) {
events.append(event)
}
}
return events
}
func getDataFromServer(forURL url: URL, completion: @escaping (_ result: [[String: Any]]) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let jsonData = data {
let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: [])
if let dictionary = jsonObject as? [String: Any] {
if let feed = dictionary["feed"] as? [String: Any] {
if let entry = feed["entry"] as? [[String: Any]] {
DispatchQueue.main.async {
completion(entry)
}
}
}
}
}
}.resume()
}
}
Your downloadEvents
function need to be asynchronous because its calls another asynchronous function getDataFromServer
.
The solution may be to move the part for calculating Events to the asynchronous block and provide a completion parameter:
func downloadEvents(completion: @escaping ([Event]) -> ()) {
var events: [Event] = []
var downloadedEvents: [[String: Any]] = []
getDataFromServer(forURL: eventsURL) { result in
downloadedEvents = result
for downloadedEvent in downloadedEvents {
if let event = Event(fromJSON: downloadedEvent) {
events.append(event)
}
}
completion(events)
}
}
Note
Your code may actually be simplified to:
func downloadEvents(completion: @escaping ([Event]) -> ()) {
getDataFromServer(forURL: eventsURL) { completion($0.compactMap(Event.init(fromJSON:))) }
}