使用Swift Data/CloudKit时出现NSKeyedUnarchiveFromData警告如何解决?
Swift Data 与 iCloud 同步时出现 NSKeyedUnarchiveFromData 警告
问题现象
- 用Swift Data做数据持久化,搭配iCloud同步时,模拟器登录iCloud就会抛出警告:
NSKeyedUnarchiveFromData不应用于解归档,未来版本将移除 - 本地保存、iCloud同步功能都正常,但日志里的警告看着闹心
- 未登录iCloud时没这个警告,确定问题出在Swift Data的iCloud同步模块
模型结构说明
模型用到了UUID、Date、自定义枚举、String、[DateComponents:[自定义类]]字典,所有类型都实现了Codable、Identifiable、Hashable协议,示例代码如下:
import Foundation import SwiftData import SwiftUI @Model class Item: Identifiable { var id = UUID() var creationDate: Date = Date() var name: String = "" var color: ColorPicked = .mint @Relationship(deleteRule: .cascade) var actions: [DateComponents:[Action]] = [:] @Relationship(deleteRule: .cascade) var charts: [Chart]? = [] init(id: UUID = UUID(), creationDate: Date, name: String, color: ColorPicked, actions: [DateComponents : [Action]], charts: [Chart]? = nil) { self.id = id self.creationDate = creationDate self.name = name self.color = color self.actions = actions self.charts = charts } } enum ColorPicked: Codable, CaseIterable, Identifiable { var id: String { return self.bgColor.description } case white case red case mint var bgColor: Color { switch self { case .white: return .white case .red: return .red case .mint: return .betterMint } } var textColor: Color { switch self { case .white: return .darkerGray default: return .white } } } class Action: Codable, Identifiable, Hashable, Observable { enum CodingKeys: CodingKey { case id, startDate, duration, amount } static func == (lhs: Action, rhs: Action) -> Bool { lhs.startDate == rhs.startDate && lhs.duration == rhs.duration && lhs.id == rhs.id && lhs.amount == rhs.amount } func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(startDate) hasher.combine(duration) } init(side: Side = .nonSelected, start: Date = Date(), duration: TimeInterval = 0, note: String = "", length: String = "", amount: Volume = Volume(ml: 0.0)) { self.startDate = start self.duration = duration self.amount = amount } var id = UUID() var startDate: Date var duration: TimeInterval var amount: Volume? required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(UUID.self, forKey: .id) self.startDate = try container.decode(Date.self, forKey: .startDate) self.duration = try container.decode(TimeInterval.self, forKey: .duration) self.amount = try container.decode(Volume.self, forKey: .amount) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(startDate, forKey: .startDate) try container.encode(duration, forKey: .duration) try container.encode(amount, forKey: .amount) } } struct Volume: Codable, Equatable { var ml: Double { didSet{ self.oz = ml / 29.574 } } var oz: Double { didSet{ self.ml = oz * 29.574 } } init(ml: Double) { self.ml = ml self.oz = ml / 29.574 } init(oz: Double){ self.oz = oz self.ml = oz * 29.574 } } class Chart: Codable, Identifiable, Hashable, Observable { enum CodingKeys: CodingKey { case id, startDate, duration } static func == (lhs: Chart, rhs: Chart) -> Bool { lhs.startDate == rhs.startDate && lhs.duration == rhs.duration && lhs.id == rhs.id } func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(startDate) hasher.combine(duration) } var id = UUID() var startDate: Date var duration: TimeInterval }
问题根源与修复方案
核心原因
Swift Data在iCloud同步某些非标准类型(比如用DateComponents做字典键、自定义类的嵌套字典结构)时,会默认回退使用已废弃的NSKeyedUnarchiver,而不是基于Codable的现代归档方式。
修复步骤
- 给
DateComponents加自定义Codable实现DateComponents本身遵循NSCoding,但默认的Codable实现不符合Swift Data的iCloud同步要求,手动写编码解码逻辑,用标准JSON格式序列化:
extension DateComponents: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(year, forKey: .year) try container.encode(month, forKey: .month) try container.encode(day, forKey: .day) // 按需加需要同步的字段,比如hour、minute等 } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.init() self.year = try container.decodeIfPresent(Int.self, forKey: .year) self.month = try container.decodeIfPresent(Int.self, forKey: .month) self.day = try container.decodeIfPresent(Int.self, forKey: .day) // 对应编码的字段解码 } enum CodingKeys: String, CodingKey { case year, month, day } }
- 调整Swift Data模型的关系结构
别直接用[DateComponents:[Action]]这种嵌套字典当@Relationship属性,拆分结构更稳妥:
新增中间模型ActionGroup,把DateComponents当属性,actions作为@Relationship数组:
@Model class ActionGroup: Identifiable { var id = UUID() var dateComponents: DateComponents @Relationship(deleteRule: .cascade) var actions: [Action] = [] init(dateComponents: DateComponents, actions: [Action]) { self.dateComponents = dateComponents self.actions = actions } }
然后在Item里替换成:
@Relationship(deleteRule: .cascade) var actionGroups: [ActionGroup] = []
这种结构符合Swift Data的设计规范,iCloud同步时会自动用现代Codable流程,不会回退到废弃的归档方式。
- 检查自定义类的Codable实现
- 确认
Action、Chart等类的所有属性都被正确编码/解码,尤其是可选类型(比如Action里的amount) - 如果不是SwiftUI视图绑定需要,去掉
Observable协议,避免干扰Swift Data的内部序列化
- 清理iCloud同步缓存
- 模拟器退出iCloud账号,删掉App重装
- 重新登录iCloud测试,确认警告消失
验证
修改后重新运行App,登录iCloud,查看控制台日志,NSKeyedUnarchiveFromData警告应该不再出现,同时数据同步功能正常。
内容的提问来源于stack exchange,提问作者Fran
相关产品推荐
相关产品推荐

