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

Foundation崩溃问题:存储KeyedDecodingContainer延迟解码时崩溃

存储KeyedDecodingContainer导致JSONDecoder偶尔崩溃的解决方案

你遇到的崩溃确实和存储KeyedDecodingContainer直接相关。KeyedDecodingContainer是JSONDecoder解码流程中的临时对象,它完全依赖Decoder的内部状态(比如原始数据的解析缓冲区、UTF8转换器等)。当HTTPBody的init(from:)方法执行完成后,JSONDecoder实例可能已经被销毁,或者其内部资源已被回收,此时再调用容器的decode方法,会访问失效的资源,引发不可预测的错误——你看到的UTF8转换失败,本质就是容器引用的原始数据缓冲区已损坏。

解决方案:存储原始JSON Data而非容器

Data是值类型,能独立保存完整的JSON内容,后续解码时用新的JSONDecoder实例处理即可,完全不依赖之前的Decoder状态。

方案一:通过Decoder的userInfo传递原始数据

import Foundation

struct HTTPBody: Decodable {
    let statusCode: Int
    private let rawData: Data?

    enum CodingKeys: CodingKey {
        case statusCode
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.statusCode = try container.decode(Int.self, forKey: .statusCode)
        // 从userInfo中获取预存的原始JSON数据
        self.rawData = decoder.userInfo[CodingUserInfoKey(rawValue: "rawData")!] as? Data
    }

    func responseBody<T: Decodable>(as decodeType: T.Type) -> T? {
        guard let data = rawData else { return nil }
        // 临时结构用于精准提取responseBody字段
        struct TempResponse: Decodable {
            let responseBody: T
        }
        return try? JSONDecoder().decode(TempResponse.self, from: data).responseBody
    }
}

使用方式:

// 原始JSON数据示例
let jsonData = """
{
    "statusCode": 412,
    "responseBody": {
      "validationErrors": [
        {
          "message": "User account cannot be created"
        }
      ]
    }
}
""".data(using: .utf8)!

var decoder = JSONDecoder()
// 将原始数据存入userInfo
decoder.userInfo[CodingUserInfoKey(rawValue: "rawData")!] = jsonData

do {
    let httpBody = try decoder.decode(HTTPBody.self, from: jsonData)
    // 定义对应模型解码responseBody
    struct ValidationErrors: Decodable {
        let validationErrors: [ValidationError]
    }
    struct ValidationError: Decodable {
        let message: String
    }
    if let errors = httpBody.responseBody(as: ValidationErrors.self) {
        print(errors.validationErrors.first?.message ?? "")
    }
} catch {
    print(error)
}

方案二:直接基于原始Data初始化(不依赖Decodable协议)

如果不需要HTTPBody遵循Decodable,这种方式更简洁:

import Foundation

struct HTTPBody {
    let statusCode: Int
    private let responseBodyData: Data?

    init(jsonData: Data) throws {
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
        guard let statusCode = jsonObject?["statusCode"] as? Int else {
            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Missing statusCode field"))
        }
        self.statusCode = statusCode
        
        // 单独提取responseBody的原始数据
        if let responseBody = jsonObject?["responseBody"] {
            self.responseBodyData = try JSONSerialization.data(withJSONObject: responseBody)
        } else {
            self.responseBodyData = nil
        }
    }

    func responseBody<T: Decodable>(as decodeType: T.Type) -> T? {
        guard let data = responseBodyData else { return nil }
        return try? JSONDecoder().decode(decodeType, from: data)
    }
}

使用方式:

do {
    let httpBody = try HTTPBody(jsonData: jsonData)
    // 后续解码逻辑同方案一
} catch {
    print(error)
}

总结

两种方案都避免了存储Decoder的容器对象,通过保存原始数据实现延迟解码,彻底消除了容器依赖外部状态带来的崩溃风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:31:06