iOS异步下载API数据后同步更新Core Data的问题求助
问题核心原因
你当前的updateAPI是同步执行逻辑,但download1是异步网络请求——发起请求后立刻去遍历download.quotes时,请求还没完成,数组里没有最新数据,自然无法正确更新Core Data。
解决方案
方案一:使用DispatchGroup等待所有请求完成
通过DispatchGroup跟踪所有异步请求的完成状态,等全部请求结束后再统一更新Core Data并保存:
修改updateAPI函数
func updateAPI() { let updateGroup = DispatchGroup() for stock in depot.aktienKatArray { guard let symbol = stock.aKat_symbol, !symbol.isEmpty else { continue } updateGroup.enter() download.download1(stock: symbol) { [weak self] result in defer { updateGroup.leave() } // 无论成功失败都标记请求完成 guard let self = self else { return } switch result { case .success(let quotes): // 直接用请求返回的结果更新,不依赖全局数组,避免数据不一致 if let targetQuote = quotes.first(where: { $0.symbol == symbol }) { // Core Data上下文线程不安全,确保在正确队列更新 PersistenceController.shared.container.viewContext.perform { stock.aKat_currPerShare = Double(targetQuote.price) ?? 0 } } case .failure(let err): print("下载\(symbol)数据失败: \(err)") } } } // 所有请求完成后,在主线程保存上下文 updateGroup.notify(queue: DispatchQueue.main) { PersistenceController.shared.saveContext() print("所有股票数据已更新并保存") } }
优化download1函数(移除冗余的内部DispatchGroup)
func download1(stock: String, completion: @escaping (Result<[Quote], NetworkError>) -> Void) { guard let url = URL(string: API.quoteUrl(for: stock)) else { completion(.failure(.invalidURL)) return } NetworkManager<GlobalQuoteResponse>().fetch(from: url) { result in switch result { case .failure(let err): print(err) completion(.failure(err)) case .success(let resp): let quotes = [resp.quote] // 在主线程更新全局数组(如果需要保留) DispatchQueue.main.async { self.quotes.append(contentsOf: quotes) } completion(.success(quotes)) } } }
方案二:使用Swift Async/Await(推荐,代码更简洁易维护)
利用Swift并发框架重构异步逻辑,摆脱回调地狱:
第一步:给NetworkManager添加异步请求方法
extension NetworkManager { func fetchAsync(from url: URL) async throws -> T { return try await withCheckedThrowingContinuation { continuation in self.fetch(from: url) { result in switch result { case .success(let data): continuation.resume(returning: data) case .failure(let err): continuation.resume(throwing: err) } } } } }
第二步:重构download1为异步函数
func download1(stock: String) async throws -> [Quote] { guard let url = URL(string: API.quoteUrl(for: stock)) else { throw NetworkError.invalidURL } let resp = try await NetworkManager<GlobalQuoteResponse>().fetchAsync(from: url) let quotes = [resp.quote] // 在主线程更新全局数组(如果需要保留) await MainActor.run { self.quotes.append(contentsOf: quotes) } return quotes }
第三步:修改updateAPI为异步函数
func updateAPI() async { for stock in depot.aktienKatArray { guard let symbol = stock.aKat_symbol, !symbol.isEmpty else { continue } do { let quotes = try await download.download1(stock: symbol) if let targetQuote = quotes.first(where: { $0.symbol == symbol }) { // 用MainActor确保在主线程更新Core Data(viewContext绑定主线程) await MainActor.run { stock.aKat_currPerShare = Double(targetQuote.price) ?? 0 } } } catch { print("更新\(symbol)数据失败: \(error)") } } // 所有更新完成后保存上下文 await MainActor.run { PersistenceController.shared.saveContext() print("所有股票数据已更新并保存") } }
调用方式(在异步上下文执行)
// 比如在ContentView的按钮点击或task修饰符中 Task { await updateAPI() }
内容的提问来源于stack exchange,提问作者SPR
相关产品推荐
相关产品推荐

