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
相关产品推荐
相关产品推荐

