iOS 18.3调用fetchHistory获取HistoryTransaction时崩溃求助
崩溃的核心是iOS 18.3强化了SwiftData历史记录的枚举校验逻辑:你的VideoPlacement枚举原本有queue成员,后来改名为queueNext并添加了原始值,但HistoryTransaction中还留存着旧的变更记录(比如之前设置placeVideosIn = .queue的操作)。iOS 18.3开始,解析这些历史记录时,SwiftData会严格检查枚举成员是否存在,找不到queue就触发致命错误。
手动修改现有模型的placeVideosIn值没用,因为崩溃触发在读取历史变更记录,而非当前模型数据——旧的历史条目依然存在,fetchHistory时还是会尝试解析废弃的枚举成员。
至于删除HistoryTransactions时偶尔丢失模型,这是操作不当导致的:如果你的删除逻辑没有精准过滤HistoryTransaction类型,可能误删了关联的模型对象;或者部分历史记录和模型的删除操作绑定,批量删除时连带删掉了模型。这不属于正常现象,是可以避免的。
1. 临时安全清理问题历史记录
不要直接删除全部HistoryTransactions,而是精准过滤并删除涉及placeVideosIn属性的旧历史条目,避免误伤模型:
import SwiftData func cleanupOldVideoPlacementHistory(in container: ModelContainer) { let context = ModelContext(container) // 只筛选涉及placeVideosIn属性变更的历史记录 let fetchDescriptor = FetchDescriptor<HistoryTransaction>( predicate: #Predicate { transaction in transaction.changes.contains(where: { $0.propertyName == "placeVideosIn" }) } ) do { let problematicTransactions = try context.fetch(fetchDescriptor) problematicTransactions.forEach { context.delete($0) } try context.save() print("成功清理旧枚举相关的历史记录") } catch { print("清理历史记录失败:\(error.localizedDescription)") } }
调用这个方法后,再执行fetchHistory就不会触发崩溃了。
2. 永久兼容方案:保留枚举旧成员作为别名
在VideoPlacement枚举中保留旧的queue成员,标记为废弃并映射到新的queueNext的原始值,让旧历史记录能被正常解析:
enum VideoPlacement: Int, Codable, PersistentEnum { case inbox = 0 // 保留旧成员,映射到新的原始值,标记为废弃 @available(*, deprecated, renamed: "queueNext") case queue = 1 case queueNext = 1 case nothing = 2 case defaultPlacement = 3 case queueLast = 4 }
这样,历史记录中的queue会被自动解析为queueNext,不会触发校验错误。等后续所有用户都完成版本更新(旧历史记录被自然清理后),再移除这个废弃的queue成员即可。
额外注意
- 后续修改枚举时,尽量避免直接删除或重命名成员,优先采用添加新成员、标记旧成员废弃的方式,保证历史记录的兼容性。
- 测试时,务必覆盖包含旧枚举值的历史记录场景,避免上线后出现类似崩溃。
内容的提问来源于stack exchange,提问作者fer0n

