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

使用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的现代归档方式。

修复步骤

  1. 给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
    }
}
  1. 调整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流程,不会回退到废弃的归档方式。

  1. 检查自定义类的Codable实现
  • 确认Action、Chart等类的所有属性都被正确编码/解码,尤其是可选类型(比如Action里的amount)
  • 如果不是SwiftUI视图绑定需要,去掉Observable协议,避免干扰Swift Data的内部序列化
  1. 清理iCloud同步缓存
  • 模拟器退出iCloud账号,删掉App重装
  • 重新登录iCloud测试,确认警告消失

验证

修改后重新运行App,登录iCloud,查看控制台日志,NSKeyedUnarchiveFromData警告应该不再出现,同时数据同步功能正常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 05:05:15