Swift中如何获取JSONDecoder.decode的解码进度以实现进度条
实现CoreDataCodable解码进度跟踪的方案
要实现解码进度条确实需要做一些自定义工作,因为默认的JSONDecoder并没有提供直接的进度回调机制,不过结合CoreDataCodable的特性,我们有几种可行的方案:
方案1:自定义带进度跟踪的ManagedObjectsUpsert
核心思路是重写ManagedObjectsUpsert的解码逻辑,在逐个解码数组元素时触发进度回调。我们可以通过Decoder的userInfo来传递进度闭包,这样就能在解码过程中实时更新进度。
代码示例
首先定义一个支持进度跟踪的自定义版本:
import CoreDataCodable struct ProgressTrackingManagedObjectsUpsert<T: ManagedObjectDecodable>: Decodable { let managedObjects: [T] enum CodingKeys: String, CodingKey { case items } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) var itemsContainer = try container.nestedUnkeyedContainer(forKey: .items) // 尝试获取数组总长度(注意:部分场景下count可能为nil,比如流式JSON) guard let totalCount = itemsContainer.count else { throw DecodingError.dataCorruptedError( forKey: .items, in: container, debugDescription: "无法获取items总数,无法跟踪进度" ) } // 从decoder的userInfo中取出进度回调 guard let progressHandler = decoder.userInfo[CodingUserInfoKey(rawValue: "decodeProgress")] as? (Double) -> Void else { throw DecodingError.dataCorruptedError( forKey: .items, in: container, debugDescription: "未设置进度回调闭包" ) } var decodedObjects: [T] = [] decodedObjects.reserveCapacity(totalCount) var currentIndex = 0 while !itemsContainer.isAtEnd { let object = try itemsContainer.decode(T.self) decodedObjects.append(object) currentIndex += 1 let progress = Double(currentIndex) / Double(totalCount) // 切换到主线程更新UI(如果需要) DispatchQueue.main.async { progressHandler(progress) } } self.managedObjects = decodedObjects } }
然后修改你的ItemsResponse结构体来使用这个自定义类型:
struct ItemsResponse: Decodable { var items: ProgressTrackingManagedObjectsUpsert<Item> enum CodingKeys: String, CodingKey { case items } }
使用方式
在解码时,把进度回调注入到JSONDecoder的userInfo中:
let decoder = JSONDecoder() // 配置CoreData上下文(CoreDataCodable必需) decoder.userInfo[.managedObjectContext] = yourManagedObjectContext // 添加进度回调 decoder.userInfo[CodingUserInfoKey(rawValue: "decodeProgress")] = { progress in // 这里更新你的进度条UI,比如: // progressView.setProgress(progress, animated: true) print("解码进度:\(String(format: "%.1f%%", progress * 100))") } do { let response = try decoder.decode(ItemsResponse.self, from: yourJSONData) // 解码完成,后续处理CoreData持久化 } catch { print("解码失败:\(error.localizedDescription)") }
方案2:拆分解码流程(适合超大JSON)
如果你的JSON数据非常大,直接一次性解析可能有内存压力,可以先解析出数组的总长度,再逐个解码元素:
- 先用
JSONSerialization把数据转成基础对象,获取items数组的count - 遍历数组,逐个解码每个元素为
Item对象 - 每解码一个元素就更新进度
这种方式需要手动处理CoreData的插入/更新,但进度跟踪更灵活,也能避免大内存占用。
注意事项
UnkeyedContainer.count不是所有情况下都能获取到:如果JSON是流式传输(比如从网络流中直接解码),count会返回nil,这时候你可能需要通过其他方式预估进度(比如根据已读取的数据字节数占总字节数的比例)。- 别忘了CoreData持久化的进度:如果
ManagedObjectsUpsert的批量保存也需要跟踪进度,可以把保存操作拆分成小批次,每完成一批就更新进度。
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

