Search code examples
iosswiftnetwork-programminggrand-central-dispatch

Waiting for the result of a network request before assigning it to a variable in Swift


I assume this question has been around many times, but I can't get my head around all the GCD and completion handling stuff. A quick fix with maybe some links to useful articles would be much appreciated.

I have a function that gets and processes data from a network request:

  func getTracklist(album id: String) -> String {

        //create a GET request

        let task = URLSession.shared.dataTask(with: request) { (data, response, error) -> Void in
            guard let data = data else { return }
            let decoder = JSONDecoder()
            if let jsonTracks = try? decoder.decode(TrackRoot.self, from: data) {
                tracks = jsonTracks.items!
            }
            //append the tracklist string
            for track in tracks {
                combinedTracks += "\(track.id)%2C"
            }
        }
            task.resume()

        return String(combinedTracks.dropLast(3))

    }

The returned value is used in another function:

formattedAlbum.trackList = self.getTracklist(album: album.id!)

As you might have guessed, the first function doesn't manage to get the data in time, and an empty string is assigned to the tracklist.

Using Alexey's answer:

for album in self.albums {
                let formattedAlbum = AlbumFormatted(context: self.persistenceManager.context)

                formattedAlbum.albumName = album.name
                self.getTracklist(album: album.id!) { (data) in
                    formattedAlbum.trackList = data
                }
                self.formattedAlbums.append(formattedAlbum)
                print(formattedAlbum)
            }
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(5)) {
                print(self.formattedAlbums[1])
            }
func getTracklist(album id: String, completion: @escaping (String?)->()) {
   //URLSession stuff

   let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            guard let data = data else { return }
            let decoder = JSONDecoder()
            if let jsonTracks = try? decoder.decode(TrackRoot.self, from: data) {
                tracks = jsonTracks.items!
                for track in tracks {
                    combinedTracks += "\(track.id),"
                }
                completion(String(combinedTracks.dropLast()))
            }
        }
            task.resume()
    }

prints:

(entity: AlbumFormatted; id: 0x6000018fb400 <x-coredata:///AlbumFormatted/tFD0D9588-2820-451B-9A00-99175026ED043>; data: {
    albumName = "Everyday Life";
    trackList = "6Tb7Zfo4PcSiS4TqQ4NnTh,1e8D1BCD2afT56Km7UahpB,45PqOIkZ9PdCjsCJQYzx9G,1cXXhzPnbrXjNQYbLdUJdy,3pcPPhPAiurm2Ior11SHrz,7jib2tJjQ82kTIZZATMvAK,0ZlVUhjO8c0bOx1D2Btznf,0UvUivL70eDwhTWBd8S38I,6VzRvCbolqcUswaS";
})

Solution

  • I see the solution using closures: Just test in your ViewController for a reference.

    override func viewDidLoad() {
        super.viewDidLoad()
        getTracklist { data in
            print(data)
        }
    }
    
    func getTracklist(completion: @escaping (Data?)->()) {
        let request = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/todos/1")!)
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // process errors here
            completion(data) // here you return your decoded data (I omit your json, as we don't have a model)
        }
        task.resume()
    }
    

    UPD After reading your comments:

    for album in self.albums { 
        let formattedAlbum = AlbumFormatted(context: self.persistenceManager.context) 
        formattedAlbum.albumName = album.name 
        self.getTracklist(album: album.id!) { data in 
            print(data) 
            formattedAlbum.trackList = data 
        }
        print(formattedAlbum.trackList) // will return you nil
        // here you will not have results in formattedAlbum.trackList, because getTracklist method processes the data asynchronously   
    }
    

    Check this, that and this SO threads.