https://developer.apple.com/documentation/foundation/predicate
在 SwiftData 中,Predicate(谓词)用于定义数据筛选条件,而 Sorting(排序)则用于指定数据检索后的顺序。SwiftData 基于 Swift 宏(如 #Predicate
)和类型安全的设计,提供了更直观的语法来构建查询。以下是详细说明及示例:
一、Predicate 基础 #
SwiftData 的 #Predicate
宏允许用 Swift 语法直接编写类型安全的查询条件,编译器会检查语法和类型错误。
基本语法结构: #
let predicate = #Predicate<T> { (object: T) in
// 条件表达式
}
二、Filter 场景示例 #
假设有一个 TodoItem
数据模型:
@Model
class TodoItem {
var title: String
var dueDate: Date
var isCompleted: Bool
var priority: Int
init(title: String, dueDate: Date, isCompleted: Bool, priority: Int) {
self.title = title
self.dueDate = dueDate
self.isCompleted = isCompleted
self.priority = Int(priority)
}
}
1. 基本比较 #
// 筛选未完成的任务
let incompletePredicate = #Predicate<TodoItem> { $0.isCompleted == false }
// 优先级大于等于3的任务
let highPriorityPredicate = #Predicate<TodoItem> { $0.priority >= 3 }
2. 字符串操作 #
// 标题包含 "urgent" 的任务(不区分大小写)
let containsPredicate = #Predicate<TodoItem> {
$0.title.localizedStandardContains("urgent")
}
// 标题以 "Fix" 开头的任务
let beginsWithPredicate = #Predicate<TodoItem> {
$0.title.localizedStandardStartsWith("Fix")
}
3. 日期比较 #
// 截止日期在今天之后的任务
let futureDuePredicate = #Predicate<TodoItem> {
$0.dueDate > Date.now
}
// 截止日期在本月内的任务
let calendar = Calendar.current
let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: .now))!
let endOfMonth = calendar.date(byAdding: DateComponents(month: 1, day: -1), to: startOfMonth)!
let thisMonthPredicate = #Predicate<TodoItem> {
$0.dueDate >= startOfMonth && $0.dueDate <= endOfMonth
}
4. 复合条件(AND/OR) #
// 高优先级(>=3)且未完成的任务
let highPriorityIncompletePredicate = #Predicate<TodoItem> {
$0.priority >= 3 && $0.isCompleted == false
}
// 标题包含 "bug" 或优先级为5的任务
let bugOrCriticalPredicate = #Predicate<TodoItem> {
$0.title.localizedStandardContains("bug") || $0.priority == 5
}
5. 集合操作 #
// 优先级在指定数组中的任务
let allowedPriorities = [1, 3, 5]
let inPredicate = #Predicate<TodoItem> {
allowedPriorities.contains($0.priority)
}
三、Sorting 场景示例 #
使用 SortDescriptor
指定排序规则,支持多个排序条件。
1. 单字段排序 #
// 按截止日期升序排列
let dueDateSort = SortDescriptor<TodoItem>(\.dueDate, order: .forward)
// 按优先级降序排列
let prioritySort = SortDescriptor<TodoItem>(\.priority, order: .reverse)
2. 多字段排序 #
// 先按是否完成排序(未完成在前),再按截止日期升序
let multiSort = [
SortDescriptor<TodoItem>(\.isCompleted, order: .forward),
SortDescriptor<TodoItem>(\.dueDate)
]
四、结合 FetchDescriptor 使用 #
将 Predicate 和 Sorting 应用到数据查询:
import SwiftData
// 创建 FetchDescriptor
let descriptor = FetchDescriptor<TodoItem>(
predicate: highPriorityIncompletePredicate, // 筛选条件
sortBy: [prioritySort, dueDateSort] // 排序规则
)
// 执行查询
let context = ModelContext(container: modelContainer)
let todos = try? context.fetch(descriptor)
五、高级用法示例 #
1. 关系型查询(假设 TodoItem
有 category
属性)
#
@Model
class Category {
var name: String
var todos: [TodoItem]
init(name: String) {
self.name = name
self.todos = []
}
}
// 筛选属于 "Work" 分类的任务
let workCategoryPredicate = #Predicate<TodoItem> {
$0.category?.name == "Work"
}
2. 使用 Optional
安全解包
#
// 筛选有分类且分类名不为空的任务
let validCategoryPredicate = #Predicate<TodoItem> {
$0.category != nil && $0.category!.name.isEmpty == false
}
3. 自定义函数扩展 #
// 扩展 Date 判断是否为今天
extension Date {
var isToday: Bool {
Calendar.current.isDateInToday(self)
}
}
// 筛选截止日期为今天的任务
let dueTodayPredicate = #Predicate<TodoItem> {
$0.dueDate.isToday
}
六、注意事项 #
- 类型安全:
#Predicate
会在编译时检查属性是否存在及类型是否匹配。 - 性能:避免在谓词中执行复杂计算,优先使用索引优化。
- 区分大小写:
localizedStandardContains
提供不区分大小写的查询,而contains
是区分大小写的。 - 空值处理:使用
Optional
类型时需显式解包(如$0.category?.name == "Work"
)。
通过灵活组合 Predicate 和 SortDescriptor,可以高效实现复杂的数据查询需求。