SwiftUI 的 #Preview 里使用 SwiftData,核心就是给预览
View 注入一个 内存版 ModelContainer。
别让Preview直接碰正式数据库,不然预览几次顺手把开发数据搞成一锅粥。
import SwiftUI
import SwiftData
@Model
final class Item {
var name: String
init(name: String) {
self.name = name
}
}
struct ContentView: View {
@Query private var items: [Item]
var body: some View {
List(items) { item in
Text(item.name)
}
}
}
#Preview {
// SwiftData 数据只保存在内存中,Preview 结束后数据直接消失
let config = ModelConfiguration(
isStoredInMemoryOnly: true
)
// 相当于给Preview创建一个独立SwiftData数据库
let container = try! ModelContainer(
for: Item.self,
configurations: config
)
container.mainContext.insert(Item(name: "苹果"))
container.mainContext.insert(Item(name: "香蕉"))
container.mainContext.insert(Item(name: "西瓜"))
// 把 ModelContainer 注入 SwiftUI Environment
return ContentView()
.modelContainer(container)
}import SwiftData
@MainActor
let previewContainer: ModelContainer = {
let config = ModelConfiguration(
isStoredInMemoryOnly: true
)
let container = try! ModelContainer(
for: Item.self,
configurations: config
)
container.mainContext.insert(Item(name: "苹果"))
container.mainContext.insert(Item(name: "香蕉"))
container.mainContext.insert(Item(name: "西瓜"))
return container
}()然后
#Preview {
ContentView()
.modelContainer(previewContainer)
}#Preview
│
▼
ModelContainer
│
▼
.modelContainer(container)
│
▼
SwiftUI Environment
│
├── @Query
│
└── @Environment(\.modelContext)一句话记忆:
#Preview + SwiftData = 创建内存 ModelContainer + .modelContainer(container)
例如
@Query(sort: \CodeBreaker.name)其中
\CodeBreaker.name表示:指向 CodeBreaker 类型的 name 属性的一条 “路径”。
普通属性访问 vs Key Path
普通访问
let user = CodeBreaker(...)
print(user.name)这里
user.name是直接获取值。而 \CodeBreaker.name 并没有获取某个对象的
name,而是描述
从 CodeBreaker 对象出发,如何找到 name。可以理解成:
CodeBreaker -> nameKey Path 本身是一个值,例如
let keyPath = \CodeBreaker.name然后:
let codeBreaker = CodeBreaker(...)
let name = codeBreaker[keyPath: keyPath]完整例子:
struct User {
let name: String
let age: Int
}
let user = User(name: "Vanno", age: 18)
let userSelfPath = \User.self
let userNamePath = \User.name
print(user[keyPath: userSelfPath])
// User(name: "Vanno", age: 18)
print(user[keyPath: userNamePath])
// Vanno在模拟器中按住不懂卸载App,会弹出确认是否同时删除Data。删除 Data 则数据库就直接一起删掉了。
| 操作 | SwiftData |
|---|---|
| Create | modelContext.insert() |
| Read | @Query / fetch() |
| Update | 直接修改属性 |
| Delete | modelContext.delete() |
| Save | try modelContext.save() |
SwiftData 的 insert、delete、修改属性后,默认情况下不一定立即写入磁盘,SwiftData 会管理 ModelContext,然后在合适的时机自动保存。
@Environment(\.modelContext) private var modelContext
let user = User(name: "Vanno")
modelContext.insert(user)insert 不等于立即写入磁盘。
User 对象
↓
insert
↓
ModelContext 中已有数据
↓
等待保存
↓
持久化存储需要立即保存:
let user = User(name: "Vanno")
modelContext.insert(user)
do {
try modelContext.save()
} catch {
print(error)
}modelContext.delete(user)同样只是告诉 ModelContext,这个东西该死了。并不代表已经立即写入持久化存储。
强制保存:
modelContext.delete(user)
do {
try modelContext.save()
} catch {
print(error)
}SwiftData 没有 update,直接修改 @Model 对象:
user.name = "New Name"SwiftData 会跟踪变化。例如:
@Model
class User {
var name: String
init(name: String) {
self.name = name
}
}更新:
user.name = "Vanno 2"强制保存:
user.name = "Vanno 2"
do {
try modelContext.save()
} catch {
print(error)
}ModelContext 有:
modelContext.autosaveEnabled默认主 ModelContext 通常启用自动保存。
print(modelContext.autosaveEnabled)自动保存一般发生在 SwiftData 认为合适的声明周期时机,如:
关键:自动保存没有 修改后立即保存 的时间保证。
modelContext.hasChanges例如
if modelContext.hasChanges {
try modelContext.save()
}封装
func saveContext() {
guard modelContext.hasChanges else {
return
}
do {
try modelContext.save()
} catch {
print("SwiftData 保存失败:", error)
}
}
// 然后
modelContext.insert(user)
saveContext()
// 或者
user.name = "Vanno"
saveContext()// 确定就是要立即保存:
try modelContext.save()
// 不想处理异常:
try? modelContext.save()
// 调试阶段甚至:
try! modelContext.save()
// 建议
do {
try modelContext.save()
} catch {
print("保存失败: \(error)")
}