如何使用Combine的sink将JSON解码结果赋值给数组
问题分析与解决方案
你的Combine实现存在三个关键问题,导致无法正确将解码后的值赋值给数组:
1. 未配置JSONDecoder的日期解码策略
原URLSession代码中明确设置了json.dateDecodingStrategy = .iso8601,但Combine代码里的JSONDecoder()使用默认初始化,没有配置该策略,导致JSON中的日期字段解码失败,触发replaceError后返回默认的[NewsItem.shared]。
2. 未在主线程更新@State变量
Combine的sink闭包默认在后台线程执行,而SwiftUI的@State变量必须在主线程更新,否则无法触发视图刷新。
3. 错误使用@State存储订阅对象
request作为Combine订阅的存储容器,不需要用@State修饰——@State仅用于视图需要响应的状态,普通变量即可满足订阅存储需求。
修正后的完整代码
struct NewsItem: Decodable { let id: Int let title: String let strap: String let url: URL let main_image: URL let published_date: Date static let shared = NewsItem(id: 0, title: "", strap: "", url: URL(string: "https://www.hackingwithswift.com/articles/239/wwdc21-wrap-up-and-recommended-talks")!, main_image: URL(string: "https://www.hackingwithswift.com/resize/300/uploads/wwdc-21@2x.jpg")!, published_date: Date()) } struct CardView: View { @State private var news = [NewsItem]() // 用普通变量存储订阅,无需@State private var cancellables = Set<AnyCancellable>() var body: some View { List { ForEach(news, id:\.id) { news in Text(news.title) Text("\(news.published_date)") Link("Goto Link", destination: news.url) AsyncImage(url: news.main_image) .frame(width: 50, height: 50) } } .onAppear { fetchData() // Combine本身是异步流,无需Task和await包裹 } } func fetchData() { let url = URL(string: "https://www.hackingwithswift.com/samples/headlines.json")! let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 // 配置日期解码策略 URLSession.shared.dataTaskPublisher(for: url) .map(\.data) .decode(type: [NewsItem].self, decoder: decoder) .replaceError(with: [NewsItem.shared]) .receive(on: DispatchQueue.main) // 切换到主线程更新UI状态 .sink(receiveValue: { items in news = items }) .store(in: &cancellables) } }
额外说明
- 若需要更细致的错误处理,可去掉
replaceError,改用sink(receiveCompletion:receiveValue:)捕获并处理错误,而非直接返回默认值。 - Combine与async/await是两种独立的异步处理方案,无需在同一个函数中混用,本场景下Combine实现完全不需要
async修饰。
内容的提问来源于stack exchange,提问作者multiverse
相关产品推荐
相关产品推荐

