You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 18:15:58