swiftasynchronousurlsessionnsurlsessiondatatask

Asynchronous thread in Swift - How to handle?


I am trying to recover a data set from a URL (after parsing a JSON through the parseJSON function which works correctly - I'm not attaching it in the snippet below).

The outcome returns nil - I believe it's because the closure in retrieveData function is processed asynchronously. I can't manage to have the outcome saved into targetData.

Thanks in advance for your help.

class MyClass {
    
    var targetData:Download?
    
    func triggerEvaluation() {
        retrieveData(url: "myurl.com") { downloadedData in
            self.targetData = downloadedData
        }
        print(targetData) // <---- Here is where I get "nil"!
    }
    
    func retrieveData(url: String, completion: @escaping (Download) -> ()) {
        let myURL = URL(url)!
        let mySession = URLSession(configuration: .default)
        let task = mySession.dataTask(with: myURL) { [self] (data, response, error) in
            if error == nil {
                if let fetchedData = data {
                    let safeData = parseJSON(data: fetchedData)
                    completion(safeData)
                }
            } else {
                //
            }
        }
        task.resume()
    }
}

Solution

  • Yes, it’s nil because retrieveData runs asynchronously, i.e. the data hasn’t been retrieved by the time you hit the print statement. Move the print statement (and, presumably, all of the updating of your UI) inside the closure, right where you set self.targetData).

    E.g.

    func retrieveData(from urlString: String, completion: @escaping (Result<Download, Error>) -> Void) {
        let url = URL(urlString)!
        let mySession = URLSession.shared
        let task = mySession.dataTask(with: url) { [self] data, response, error in
            guard 
                let responseData = data,
                error == nil, 
                let httpResponse = response as? HTTPURLResponse,
                200 ..< 300 ~= httpResponse.statusCode
            else {
                DispatchQueue.main.async {
                    completion(.failure(error ?? NetworkError.unknown(response, data))
                }
                return
            }
    
            let safeData = parseJSON(data: responseData)
            DispatchQueue.main.async {
                completion(.success(safeData))
            }
        }
        task.resume()
    }
    

    Where

    enum NetworkError: Error {
        case unknown(URLResponse?, Data?)
    }
    

    Then the caller would:

    func triggerEvaluation() {
        retrieveData(from: "https://myurl.com") { result in
            switch result {
            case .failure(let error):
                print(error)
                // handle error here
    
            case .success(let download):
                self.targetData = download
                // update the UI here
                print(download)
            }
        }
        // but not here
    }
    

    A few unrelated observations: