Filter and Sorting — Predicate

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. 关系型查询(假设 TodoItemcategory 属性) #

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

六、注意事项 #

  1. 类型安全#Predicate 会在编译时检查属性是否存在及类型是否匹配。
  2. 性能:避免在谓词中执行复杂计算,优先使用索引优化。
  3. 区分大小写localizedStandardContains 提供不区分大小写的查询,而 contains 是区分大小写的。
  4. 空值处理:使用 Optional 类型时需显式解包(如 $0.category?.name == "Work")。

通过灵活组合 Predicate 和 SortDescriptor,可以高效实现复杂的数据查询需求。

本文共 1052 字,创建于 Feb 28, 2025
相关标签: Xcode, SwiftUI