如何在Swift同一函数中用双URLSession请求获取Spotify两类数据?
如何在Swift中通过Spotify API同时获取热门歌手与热门曲目数据
步骤1:定义组合数据结构
首先创建一个结构体,用来封装热门歌手(top artists)和热门曲目(top tracks)的数据,让completionHandler可以一次性返回两类结果:
// 假设你已经有解析Spotify返回数据的Artist和Track模型(符合Codable) struct TopMusicData { let artists: [Artist] let tracks: [Track] }
方式一:用Async/Await实现(iOS 15+ / macOS 12+ 推荐)
如果项目支持Swift并发特性,这种方式代码更简洁易维护,无需手动管理异步任务的同步:
func fetchTopMusicData(timeRange: String = "medium_term", limit: Int = 20) async throws -> TopMusicData { let session = URLSession.shared let accessToken = "你的Spotify访问令牌" // 需要确保令牌拥有user-top-read权限 // 1. 请求并解析热门歌手数据 guard let artistsURL = URL(string: "https://api.spotify.com/v1/me/top/artists?time_range=\(timeRange)&limit=\(limit)") else { throw URLError(.badURL) } var artistsRequest = URLRequest(url: artistsURL) artistsRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") let (artistsData, _) = try await session.data(for: artistsRequest) let artistsResponse = try JSONDecoder().decode(TopArtistsResponse.self, from: artistsData) let topArtists = artistsResponse.items // 2. 请求并解析热门曲目数据 guard let tracksURL = URL(string: "https://api.spotify.com/v1/me/top/tracks?time_range=\(timeRange)&limit=\(limit)") else { throw URLError(.badURL) } var tracksRequest = URLRequest(url: tracksURL) tracksRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") let (tracksData, _) = try await session.data(for: tracksRequest) let tracksResponse = try JSONDecoder().decode(TopTracksResponse.self, from: tracksData) let topTracks = tracksResponse.items // 返回组合数据 return TopMusicData(artists: topArtists, tracks: topTracks) } // 配套的解析模型示例(需符合Codable) struct TopArtistsResponse: Codable { let items: [Artist] } struct TopTracksResponse: Codable { let items: [Track] } struct Artist: Codable { let id: String let name: String let images: [SpotifyImage]? } struct Track: Codable { let id: String let name: String let artists: [Artist] let album: Album } struct SpotifyImage: Codable { let url: String let height: Int? let width: Int? } struct Album: Codable { let id: String let name: String let images: [SpotifyImage]? }
调用示例:
Task { do { let topMusicData = try await fetchTopMusicData() // 处理获取到的歌手和曲目数据 print("热门歌手:\(topMusicData.artists.map { $0.name })") print("热门曲目:\(topMusicData.tracks.map { $0.name })") } catch { print("请求失败:\(error.localizedDescription)") } }
方式二:用DispatchGroup实现回调式请求(兼容iOS 14及以下)
如果需要兼容旧系统,可使用DispatchGroup来同步两个异步请求的完成状态:
func fetchTopMusicData(timeRange: String = "medium_term", limit: Int = 20, completion: @escaping (Result<TopMusicData, Error>) -> Void) { let session = URLSession.shared let accessToken = "你的Spotify访问令牌" let dispatchGroup = DispatchGroup() var topArtists: [Artist]? var topTracks: [Track]? var requestError: Error? // 发起热门歌手请求 dispatchGroup.enter() guard let artistsURL = URL(string: "https://api.spotify.com/v1/me/top/artists?time_range=\(timeRange)&limit=\(limit)") else { completion(.failure(URLError(.badURL))) return } var artistsRequest = URLRequest(url: artistsURL) artistsRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") session.dataTask(with: artistsRequest) { data, _, error in defer { dispatchGroup.leave() } if let error = error { requestError = error return } guard let data = data else { requestError = URLError(.cannotParseResponse) return } do { let response = try JSONDecoder().decode(TopArtistsResponse.self, from: data) topArtists = response.items } catch { requestError = error } }.resume() // 发起热门曲目请求 dispatchGroup.enter() guard let tracksURL = URL(string: "https://api.spotify.com/v1/me/top/tracks?time_range=\(timeRange)&limit=\(limit)") else { completion(.failure(URLError(.badURL))) return } var tracksRequest = URLRequest(url: tracksURL) tracksRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") session.dataTask(with: tracksRequest) { data, _, error in defer { dispatchGroup.leave() } if let error = error { requestError = error return } guard let data = data else { requestError = URLError(.cannotParseResponse) return } do { let response = try JSONDecoder().decode(TopTracksResponse.self, from: data) topTracks = response.items } catch { requestError = error } }.resume() // 等待两个请求完成后返回结果 dispatchGroup.notify(queue: .main) { if let error = requestError { completion(.failure(error)) return } guard let artists = topArtists, let tracks = topTracks else { completion(.failure(URLError(.unknown))) return } completion(.success(TopMusicData(artists: artists, tracks: tracks))) } }
调用示例:
fetchTopMusicData { result in switch result { case .success(let data): print("热门歌手:\(data.artists.map { $0.name })") print("热门曲目:\(data.tracks.map { $0.name })") case .failure(let error): print("请求失败:\(error.localizedDescription)") } }
关键注意事项
- 权限要求:你的Spotify访问令牌必须拥有
user-top-read权限,否则API会返回403错误。 - 令牌有效性:确保访问令牌未过期,可通过Spotify的授权流程刷新令牌。
- 模型适配:根据你需要的字段调整
Artist、Track等模型的属性,完全匹配Spotify API返回的JSON结构。
内容的提问来源于stack exchange,提问作者ezhovich
相关产品推荐
相关产品推荐

