SwiftData 入门笔记

了解 SwiftData 模型定义、关联关系、容器与上下文配置、增删改查等,实现基本业务功能。

对于复杂应用场景,可查阅文档: https://www.hackingwithswift.com/quick-start/swiftdata/

模型定义

通过 File - New - Empty File 创建一个新文件

  • 创建类,实现 Identifiable 协议,它的主要作用是为类型提供稳定的身份标识,必须提供一个 id 属性,用于唯一标识该类型的每个实例。
  • import SwiftData 并给类声明 @Model 宏,定义它为 SwiftData 模型
  • 类需要 init 方法,在类中的某个地方键入 i,会自动生成 init 方法,可修改 init 方法的默认值
  • 使用@Attribute(.unique)标记唯一标识符id
  • 定义一对多模型关联关系,引入其他 SwiftData 模型为数组。

比如创建一个 ProgressItem 进度事项模型:

import Foundation
import SwiftData

@Model
final class ProgressItem: Identifiable {
    @Attribute(.unique) var id: UUID
    var title: String
    var itemDescription: String
    var totalAmount: Double
    var completedAmount: Double
    var unit: String
    var isCompleted: Bool
    var isAbandoned: Bool
    var createdDate: Date
    var lastUpdated: Date
    var progressRecords: [ProgressRecord]?
    
    init(title: String, description: String, totalAmount: Double, unit: String) {
        self.id = UUID()
        self.title = title
        self.itemDescription = description
        self.totalAmount = totalAmount
        self.completedAmount = 0.0
        self.unit = unit
        self.isCompleted = false
        self.isAbandoned = false
        self.createdDate = Date()
        self.lastUpdated = Date()
        self.progressRecords = []
    }
}

ProgressRecord 进展记录模型:

import Foundation
import SwiftData

@Model
final class ProgressRecord: Identifiable {
    @Attribute(.unique) var id: UUID
    var amount: Double
    var note: String?
    var date: Date
    init(amount: Double, note: String? = nil) {
        self.id = UUID()
        self.amount = amount
        self.note = note
        self.date = Date()
    }
}

到 iTourApp.swift 也 import SwiftData ,并给 WindowGroup 添加此修饰符: .modelContainer(for: Destination.self)

数据查询

使用@Query属性包装器来声明式地获取数据:

@Query private var progressItems: [ProgressItem]

@Query 可传 filter 参数对参数进行过滤:

@Query(filter: #Predicate<ProgressItem> { $0.isCompleted == true })
private var completedItems: [ProgressItem]

也可以在视图中过滤:

ForEach(progressItems.filter { !$0.isCompleted && !$0.isAbandoned })

模型容器与上下文

在 App 入口文件中配置共享的模型容器:

var sharedModelContainer: ModelContainer = {
    let schema = Schema([
        ProgressItem.self,
        ProgressRecord.self,
    ])
    let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
    do {
        return try ModelContainer(for: schema, configurations: [modelConfiguration])
    } catch {
        fatalError("Could not create ModelContainer: \(error)")
    }
}()

通过 .modelContainer 修饰符提供模型上下文

在预览中:

#Preview {
    ContentView().modelContainer(for: [ProgressItem.self, ProgressRecord.self])
}

在应用主体中:

var body: some Scene {
   WindowGroup {
       ContentView()
   }
   .modelContainer(sharedModelContainer)
}

在组件中获取 SwiftData 的模型上下文:

  • modelContext 是数据操作的核心入口,用于管理实体对象的创建、查询、更新和删除
  • 无需手动创建,通过 @Environment 自动从父视图继承,确保数据操作的一致性
@Environment(\.modelContext) private var modelContext

模型上下文用于插入新对象、删除对象和保存更改:

// 插入新对象
modelContext.insert(newItem)
// 删除对象
modelContext.delete(item)
// 保存更改
try modelContext.save()

增删改数据操作

插入数据 ,如创建新事项并插入:

let newItem = ProgressItem(title: title, description: description, totalAmount: totalAmountValue, unit: unit)
modelContext.insert(newItem)

更新数据,如更新事项属性并保存:

progressItem.completedAmount += amount
progressItem.lastUpdated = Date()
try modelContext.save()

删除数据 ,如删除事项:

modelContext.delete(item)
try modelContext.save()

处理迁移

如果 SwiftData 的模型结构发生了不兼容的更改,但本地已有旧的数据存储文件,会导致 SwiftData 无法加载。

开发阶段可删除模拟器/真机上的 App,清除数据,重新运行项目即可。

生产环境下,模型变更要用数据迁移,否则用户数据会丢失。

  • 简单变更(如添加新属性、重命名属性等),可自动执行轻量级迁移。
  • 当模型发生破坏性变更(删除属性、修改关系、类型变更等)时,需要自定义迁移计划,定义、创建和应用。