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

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数据非常大,直接一次性解析可能有内存压力,可以先解析出数组的总长度,再逐个解码元素:

  1. 先用JSONSerialization把数据转成基础对象,获取items数组的count
  2. 遍历数组,逐个解码每个元素为Item对象
  3. 每解码一个元素就更新进度

这种方式需要手动处理CoreData的插入/更新,但进度跟踪更灵活,也能避免大内存占用。

注意事项

  • UnkeyedContainer.count不是所有情况下都能获取到:如果JSON是流式传输(比如从网络流中直接解码),count会返回nil,这时候你可能需要通过其他方式预估进度(比如根据已读取的数据字节数占总字节数的比例)。
  • 别忘了CoreData持久化的进度:如果ManagedObjectsUpsert的批量保存也需要跟踪进度,可以把保存操作拆分成小批次,每完成一批就更新进度。

内容的提问来源于stack exchange,提问作者Robert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 08:13:12