URLSession数据任务在结构体遵循Identifiable协议时返回Nil
解决Spotify API数据在SwiftUI ForEach中使用的解码与Identifiable兼容问题
问题场景
我调用Spotify API获取最近播放曲目:
https://api.spotify.com/v1/me/player/recently-played?limit=2&before=1676369852305
原本请求能正常获取结果,但为了在SwiftUI的List/ForEach循环中使用数据,给结构体添加Identifiable协议及id属性后,JSON解码器返回Nil(解码失败)。运行SwiftUI代码时还报错:
Referencing initializer on 'ForEach' requires that 'Item' conform to 'Identifiable'
相关代码如下:
// 结构体定义 struct Recent: Codable { // 或Codable, Identifiable //let id: UUID? let items: [Item]? let next: String? let limit: Int? let href: String? } struct Item: Codable { // 或Codable, Identifiable // let id: UUID? let track: Track? let played_at: String? } struct Track: Codable { // 或Codable, Identifiable //let id: String? let name: String? let popularity: Int? let trackNumber: Int? let type, uri: String? } // 请求函数 func getRecentPlayed(miliTime: String) { let urlString = "https://api.spotify.com/v1/me/player/recently-played?limit=10&before=\(miliTime)" let url = URL(string: urlString) let urlSessionConfig = URLSessionConfiguration.default urlSessionConfig.httpAdditionalHeaders = ["Authorization": "Bearer \(OAuthToken)"] let urlSession = URLSession(configuration: urlSessionConfig) let sessionTask = urlSession.dataTask(with: url!) { data, response, error in if (error == nil && data != nil) { let jsondecoder = JSONDecoder() let recentPlayedData = try? jsondecoder.decode(Recent.self, from: data!) print (recentPlayedData) recent = recentPlayedData } else { print (error) } } sessionTask.resume() } // SwiftUI代码 @State var recent: Recent? if let r = recent { NavigationView { List { ForEach(r.items!) { item in Text((item.track?.name)!) } } .navigationTitle("Recently played") } }
解决方案
1. 让Item结构体兼容Identifiable且不破坏解码
Spotify返回的Item没有自带id字段,但可以用唯一的played_at(播放时间戳)作为标识,通过计算属性实现Identifiable,不会影响JSON解码:
struct Item: Codable, Identifiable { // 用played_at作为唯一id,计算属性无需JSON对应字段 var id: String { played_at! } let track: Track? let played_at: String? }
2. 修复Track结构体的字段映射错误
Spotify API返回的曲目编号字段是track_number(下划线命名),而你的结构体用了驼峰式trackNumber,会导致该字段解码失败。添加CodingKeys做映射:
struct Track: Codable { let name: String? let popularity: Int? let trackNumber: Int? let type, uri: String? enum CodingKeys: String, CodingKey { case name, popularity, type, uri case trackNumber = "track_number" } }
3. 优化SwiftUI的可选值处理
避免强制解包导致崩溃,用可选绑定和默认值替代:
@State var recent: Recent? NavigationStack { // iOS16+推荐用NavigationStack替代NavigationView List { if let items = recent?.items { ForEach(items) { item in Text(item.track?.name ?? "Unknown Track") } } else { Text("No recent tracks found") } } .navigationTitle("Recently played") }
4. 修复解码错误排查与线程问题
原代码用try?会吞掉错误,换成do-catch打印具体错误;同时必须在主线程更新UI状态:
let jsondecoder = JSONDecoder() do { let recentPlayedData = try jsondecoder.decode(Recent.self, from: data!) print(recentPlayedData) // 回到主线程更新@State变量 DispatchQueue.main.async { recent = recentPlayedData } } catch { print("解码错误: \(error)") }
内容的提问来源于stack exchange,提问作者ccmsd18
相关产品推荐
相关产品推荐

