更多 SwiftData

在 Preview 中使用 SwiftData

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)
}

封装成 Preview 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)

Key Path 键路径 \

例如

@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 -> name

Key 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 则数据库就直接一起删掉了。

CRUD 对应关系

操作 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)")
}