WWDC23 初识 SwiftData

发布
更新
字数 864
阅读 5 分钟
阅读量 3007

SwiftData 是 WWDC 2023 发布的一个功能强大的数据建模、持久化管理框架,Swift 原生代码,无需引入额外文件(如 Core Data 的 data model 描述文件)。无缝接入 SwiftUI 声明式语法,实现建模,执行查找、过滤操作。这一切都得益于新的 Swift Macros 特性。

此外 SwiftData 结合 DocumentGroup 可以很好地支持文档类型的应用,参考示例项目 https://developer.apple.com/documentation/SwiftUI/Building-a-document-based-app-using-SwiftData

使用 model 宏建模

@Model 是新的 Swift 宏,用来定义模型 schema。同时 Swift Data 还提供了丰富的接口用于定义模型属性、关系等。

import SwiftData

// 声明模型类,@Model 会自动创建 schema
@Model
class Trip {
    // 自定义 attribute
    @Attribute(.unique) var name: String
    var destination: String
    var endDate: Date
    var startDate: Date

    // 自定义关系
    @Relationship(.cascade) var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}

模型属性支持基本的值类型,如 string、int、float 类型,同时也支持其他复杂的类型如 struct、enum 类型(需要其符合 Codable 协议)以及集合类型。如果不想为某一个属性建模,可以使用 @Transient

Container

模型容器为应用提供了持久化支持,可以参考 Core Data 栈中的相关概念。同样的,Cotainer 可以使用默认设置,也可以自定义如 URL、CloudKit、group container identifiers 和迁移选项等。设置好 container 就可以执行 CRUD 操作了。

设置 Container

import SwiftData

// 最简单的方式,只用 schema 初始化
let container = try ModelContainer([Trip.self, LivingAccommodation.self])

// 或者,设置 schema ,同时进行一些自定义
let container = try ModelContainer(
    for: [Trip.self, LivingAccommodation.self],
    configurations: ModelConfiguration(url: URL("path"))
)

使用 SwiftUI 修改器,注入 container

import SwiftUI

@main
struct TripsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(
            for: [Trip.self, LivingAccommodation.self]
        )
    }
}

访问 model context

Model context 用于执行一系列数据操作:

  • 跟踪修改
  • 获取数据
  • 保存修改
  • 撤销修改

在 SwiftUI 中设置好 container 后,使用视图环境变量访问 model context:

import SwiftUI

struct ContextView : View {
    @Environment(\.modelContext) private var context
}

如果不是在视图层访问模型上下文,可以直接使用 container 实例化:ModelContext(container)

访问数据

Swift 提供了新的原生类型,PredicateFetchDescriptor ,以及改进的 SortDescriptor

#Predicate 宏创建查询谓词

let today = Date()
let tripPredicate = #Predicate<Trip> { 
    $0.destination == "New York" &&
    $0.name.contains("birthday") &&
    $0.startDate > today
}

查询

let descriptor = FetchDescriptor<Trip>(
    // Sort
    sortBy: SortDescriptor(\Trip.name),
	predicate: tripPredicate
)

// Fetch
let trips = try context.fetch(descriptor)

使用 Model Context 管理数据

var myTrip = Trip(name: "Birthday Trip", destination: "New York")

// 插入数据
context.insert(myTrip)

// 删除数据
context.delete(myTrip)

// 手动保存更改
try context.save()

注意,当通过视图修改或用户输入数据后,SwiftData 会自动保存,所以 context.save() 不是必需的。只有一些特殊情况,即你需要确保数据已经保存好的时候,如你需要从自己的应用分享刚刚编辑的内容,才需要显式的调用该方法。

@Query

使用 Query 可以高效地查询大型数据集,并自定义返回内容的方式,如排序、过滤等。

import SwiftUI

struct ContentView: View  {
    // 支持排序、过滤等操作
    @Query(sort: \.startDate, order: .reverse) var trips: [Trip]
    @Environment(\.modelContext) var modelContext
    
    var body: some View {
       NavigationStack() {
          List {
             ForEach(trips) { trip in 
                 // ...
             }
          }
       }
    }
}

本文基于 WWDC23 Meet SwiftData 完成 https://developer.apple.com/videos/play/wwdc2023/10187/