
概述
在列位头发茂盛小码农们的撸码生涯中,大概都会遇到各种千奇百怪的问题。它们有的是真的难,而有的仅是看起来很难,实际只是一些"赳赳武夫"。当我们真正洞悉它们的根本原因后,可能会哭笑不得。

而这篇故事中的问题无疑属于后者,貌似不寒而栗,实则仅一只"纸老虎"而已。
在本篇博文中,您将学到如下内容:
-
- 问题来了
-
- ChatGPT、Deepseek、豆包均"无一幸免"
- 5.1 ChatGPT(4o)
- 5.2 Deepseek
- 5.3 豆包
-
- 回到起点:恍然大悟!?
相信学完本课后的宝子们,将会对 SwiftData 的基本知识有更加深入的理解和掌控。
那还等什么呢?让我们马上开始 SwiftData 爆锤"纸老虎"大冒险吧! Let's go!!!8-)
4. 问题来了
为了最大化利用 Xcode 项目代码丰富的上下文,我们决定在原有项目中新建一个 Playground 来施展拳脚。
在新建的 Playground 文件中,贴入如下代码:
swift
import Foundation
import SwiftUI
import SwiftData
@testable import AppProjectName // 可省略
let container = ModelContainer.preview
let context = container.mainContext
let settings = try SingletonSettings.getShared(context)
settings.idiomsPageCount = 20
let sharedSettings = try SingletonSettings.getShared(context)
print("count is \(sharedSettings.idiomsPageCount)")
然而,就是这样一段简单的不能再简单测试代码的运行结果竟会让我们"目瞪口呆":

从上图中可以看到,我们在把共享 settings 的 idiomsPageCount 设置为 20 后,再次尝试获取的共享 settings 竟然回退到了原始的值。并且从上面的代码中,我们嗅出了一丝不太妙的气味:我们实际是在不停反复重建本应单例的 SingletonSettings 共享对象!
从问题的表现来看,一般出现这种情况可能的原因不外乎有以下几种:
- 使用了不同的 ModelContext 对象;
- 没有将创建后的 SingletonSettings 实例插入到上下文中;
- 创建 SingletonSettings 后没有保存;
但是,通过观察我们的实现可以确保上述情况都不存在。
如果大家回顾之前 SingletonSettings 的实现代码,会发现一切看起来都是那么的无懈可击。
那么,这到底是"肿么回事"呢?
5. ChatGPT、Deepseek、豆包均"无一幸免"
在揭晓最终那个出乎意料简单的答案之前,我们觉得有必要先让各大 AI 深度思考引擎来尝试解决一下这个问题。
5.1 ChatGPT(4o)
ChatGPT 给我们的解释有点让人摸不着头脑,看似说了很多但其实啥也没说:

可以肯定的是,这个解释和根本症结差了不止十万八千里。
5.2 Deepseek
我们再来看看 Deepseek 的表现:

这个回答就更离谱了,它所说的 3 个问题(UUID 比较方式、缺乏唯一约束、上下文未同步)和实际的原因不能说离题太远,也基本是毫不相干。
5.3 豆包
最后来看看豆包 AI 编程引擎的应对:

不出所料,豆包在这个问题上基本也是不知所云。
当然比较 UUID 字符串在这里可能会造成程序崩溃,但这并不是该问题的"罪魁祸首"。
在上面 3 个"能打的" AI 都惘然若失之后,我们又该何去何从呢?
6. 回到起点:恍然大悟!?
其实,这种问题都有一个基本的特征:看起来实现完美无瑕,仿佛是库克在和我们故意作对一样。
当出现这种"绝不可能出错"却事与愿违的情况,可能往往就是非常简单的地方我们没有考虑到。
这时,最优的调试策略是:
- 把最新的 m4 max 笔记本从窗户扔出去;
- 点一杯超大杯冰镇可乐 + 牛排细细品味;
- 玩半个小时暗黑破坏神2重制版;
- 眺望远处男生或女生宿舍 30 分钟(取决于你是程序猿还是程序媛);
- 用"最小化隔离"方法创建一个小项目来重新测试;
在用 SwiftData 模版重新创建项目测试后发现,并不存在之前那个问题。
所以如果不是我们之前出现了幻觉,基本可以确定 SwiftData 本身的实现逻辑是没有任何错误滴,库克也无需为此背锅了。
仔细检查新旧项目的不同之处,我们立即察觉到:旧项目中 SingletonSettings 是后来添加的 Model 类,我们少做了一件非常简单且非常重要的事!那就是在模型容器中注册它!
回到我们 ModelContainer 的实现中,为其 schema 添加 SingletonSettings 类型:
swift
extension ModelContainer {
fileprivate static let schema = Schema([
Idiom.self,
IdiomManager.self,
SingletonSettings.self, // 之前少了这一句 T_T
])
private static func create(memoryOnly: Bool = true) throws -> ModelContainer {
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: memoryOnly)
return try ModelContainer(for: schema, configurations: [modelConfiguration])
}
static var shared = try! create(memoryOnly: false)
static let preview = try! create()
static var auto = ProcessInfo.processInfo.isRunningInPreview ? ModelContainer.preview : .shared
}
在上面的实现中,我们仅仅添加了一行代码,即在 ModelConfiguration 的配置(schema)中注册新建的 SingletonSettings 类型。
现在,再次运行测试代码,你会发现我们已经正确获取到了唯一的 SingletonSettings 单例对象,也成功修改了其 idiomsPageCount 属性,整个世界都变得清净了:

由此,我们得到了 SwiftData 框架中的一个重要特性:如果 Model 类没有在模型容器中注册,它的实例不会被正确创建和保存。
看到这,肯定有秃头小码农们会说:这么简单,我早就看出来了!
如果是这样,那么恭喜!你们真是"火眼金睛",对 SwiftData 的感觉已相当 Nice;如果没有看出来也没有所谓,毕竟有时人在钻牛角尖时是会六亲不认的。
总结
在本篇文章中,我们进一步深入剖析了这个 SwiftData 里"刁钻古怪"的问题,在各大 AI 引擎皆"全军覆没"后,我们人肉给出了问题最根本的原因和解决之道。
感谢观赏,再会啦!8-)