0

I want to retrieve top artists and tracks data using Spotify's APIs. Here is my code for getting top artists data:

func getTopArtistsData(accessToken: String, timeRange: String, completionHandler: @escaping (TopArtistsData?, Error?) -> Void){
        var request = URLRequest(url: URL(string: "https://api.spotify.com/v1/me/top/artists?time_range=\(timeRange)")!)
        request.httpMethod = "GET"
        request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

        URLSession.shared.dataTask(with: request) { (data, response, error) in
            let result = self.parseJSONTopArtists(topData: data!)
            completionHandler(result, nil)
        
        }.resume()
    }

But I want to do a second request with api.spotify.com/v1/me/top/tracks in the same func, so it the completionHandler will return both artists and tracks data. How to do this?

ezhovich
  • 19
  • 3
  • 1
    Time to learn about async/await, this would be quite easy using `async let`. – Joakim Danielson May 19 '23 at 11:01
  • async/await or use a DispatchGroup, to know when the two calls have been made and then you can call your `completionHandler`: https://stackoverflow.com/a/51262515/1801544 – Larme May 19 '23 at 13:55

1 Answers1

1

You have several options here.

  1. Modern technologies -> async/await
  2. Good old GCD -> DispatchGroup as suggested by @Larme in comments to your question. (parallel running)
  3. Chain requests in dirty way inside getTopArtistsData. (serial running)

To not overcomplicate GCD should work fine here.

func getTopArtistsData(accessToken: String, timeRange: String, completionHandler: @escaping (TopArtistsData?, TopTracksData?) -> Void) {

    var artistsRequest = URLRequest(url: URL(string: "https://api.spotify.com/v1/me/top/artists?time_range=\(timeRange)")!)
    artistsRequest.httpMethod = "GET"
    artistsRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

    var tracksRequest = URLRequest(url: URL(string: "https://api.spotify.com/v1/me/top/tracks")!)
    tracksRequest.httpMethod = "GET"
    tracksRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

    let group = DispatchGroup()

    var topArtists: TopArtistsData?
    var topTracks: TopTracksData?

    group.enter()
    URLSession.shared.dataTask(with: artistsRequest) { (data, response, error) in
        topArtists = self.parseJSONTopArtists(topData: data!)
        group.leave()
    }.resume()

    group.enter()
    URLSession.shared.dataTask(with: tracksRequest) { (data, response, error) in
        topTracks = self.parseJSONTopTracks(topData: data!)
        group.leave()
    }.resume()

    group.notify(queue: DispatchQueue.main) {
        completionHandler(topArtists, topTracks)
    }
}

Please note this is very rough example of what you need, but it should do the trick.

There are still a lot of things to take into consideration like eliminate force unwrapping, do not capture self strongly in network requests, error handling and so on.

PS: URLSession data task completion block with (data, response, error) is not called on main thread, so attempt to update UI will lead to problems. In my example DispatchGroup will execute it's block on main thread, so we are safe here.