@FetchRequest 在 SwiftUI 中 — 是一个用于从 Core Data 自动获取数据并反应式更新界面的 property wrapper。该 wrapper 接受带有过滤谓词和排序的 NSFetchRequest,结果 — NSManagedObject 数组 — 会在 Core Data 上下文中的任何更改时自动更新。根据 Apple Developer Documentation (2025),@FetchRequest 需要在 SwiftUI 环境中提供 managedObjectContext,并支持动态谓词,在 Core Data 和用户界面之间提供反应式连接。
要点
@FetchRequest — 是一个 SwiftUI property wrapper,用于创建和管理来自 Core Data 的 FetchRequest。在初始化时,wrapper 接受实体类型、用于排序的 NSSortDescriptor 数组和用于过滤的可选 NSPredicate。结果存储为指定 NSManagedObject 类型的对象数组,并在持久化层发生变化时自动更新。
@FetchRequest 的关键特性是反应性。当 Core Data 中的数据发生变化时(save、insert、delete、update),SwiftUI 会自动重新获取并重绘所有使用此 fetch request 的 View。开发者无需手动调用 refresh、订阅 NSManagedObjectContextDidSaveNotification 或重新加载数据。SwiftUI 管理整个同步周期。
@FetchRequest 工作需要 NSManagedObjectContext 在 SwiftUI 环境中通过 @Environment(\.managedObjectContext) 可用。上下文通常从创建 NSPersistentContainer 的应用程序根目录传递。标准模式 — 使用 ViewModel 或 App 结构配置 Core Data 堆栈,并通过 .environment() 修改器传递上下文。
@FetchRequest 接受多个参数来配置获取。SortDescriptors — 确定记录顺序的 NSSortDescriptor 数组。Predicate — 用于过滤的 NSPredicate,例如仅获取状态为“已完成”的任务。Animation — 数据变化时更新列表的动画。
Core Data 谓词使用类似 SQL 的表达式格式:NSPredicate(format: “status == %@”, “completed”)。多个条件可以通过 AND、OR 和 NOT 组合。SortDescriptors 确定顺序:NSSortDescriptor(keyPath: \Task.dueDate, ascending: true)。要按多个字段排序,传递描述符数组 — 第一个作为主要排序键,其余作为次要排序键。
struct TaskListView: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(keyPath: \.Task.dueDate, ascending: true),
NSSortDescriptor(keyPath: \.Task.priority, ascending: false)
],
predicate: NSPredicate(format: "isCompleted == NO"),
animation: .default
)
var tasks: FetchedResults<Task>
var body: some View {
List(tasks) { task in
Text(task.title ?? "")
}
}
}
在示例中,@FetchRequest 选择所有未完成的任务 (isCompleted == NO),首先按日期(升序)排序,然后按优先级(降序)排序。FetchedResults<Task> — 一个自动更新的集合,对 ForEach 和 List 表现为数组。.default 动画确保记录在变化时平滑地显示/消失。
@FetchRequest 与 List 和 ForEach 完美结合,用于显示动态 Core Data 列表。获取结果 (FetchedResults) 符合 RandomAccessCollection 协议,因此可以直接在 List 和 ForEach 中使用。在添加、删除或修改记录时,列表会自动以动画方式重绘。
删除记录使用上下文的 delete 方法。添加 — 创建新的 NSManagedObject 并调用 save。所有更改通过 viewContext 执行,viewContext 与 @FetchRequest 一起确保数据一致性。对于编辑,对特定的 Core Data 对象使用 @ObservedObject 或 @Bindable。
struct TaskListWithActions: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \.Task.createdAt,
ascending: false)]
)
var tasks: FetchedResults<Task>
var body: some View {
List {
ForEach(tasks) { task in
HStack {
Text(task.title ?? "")
Spacer()
Image(systemName: task.isCompleted
? "checkmark.circle.fill"
: "circle")
}
.onTapGesture { toggleTask(task) }
}
.onDelete(perform: deleteTasks)
}
.toolbar {
Button(systemImage: "添加") { addTask() }
}
}
private func addTask() {
let newTask = Task(context: viewContext)
newTask.title = "New task \(tasks.count + 1)"
newTask.createdAt = Date()
try? viewContext.save()
}
private func toggleTask(_ task: Task) {
task.isCompleted.toggle()
try? viewContext.save()
}
private func deleteTasks(at offsets: IndexSet) {
for index in offsets {
viewContext.delete(tasks[index])
}
try? viewContext.save()
}
}
此示例演示了使用 @FetchRequest 的完整 CRUD 操作。添加:通过上下文创建 Task 并 save()。删除:在上下文中 delete() 并 save()。更新:切换属性并 save()。每次 save() 后,@FetchRequest 自动重新请求数据,List 以动画方式更新。onDelete 和 onTapGesture 修改器提供标准的 iOS 交互。
@FetchRequest 通过 predicate 参数支持动态谓词,该参数可在运行时更改。为此,您需要使用变量谓词创建 @FetchRequest,并通过 Binding 传递它。当用户更改过滤器(例如选择任务类别)时,谓词会更新,获取将自动重启。
动态谓词通过 @State 或 @Published 属性实现,该属性作为 Binding 传递给 @FetchRequest 的初始化器。SwiftUI 跟踪此 Binding 的更改,并使用新谓词重新创建 NSFetchRequest,自动更新列表。
让我们看一个使用 @FetchRequest 获取记录的完整日记应用程序示例。搜索表单使用动态谓词:输入文本时,FetchRequest 自动更新结果,按标题和内容过滤记录。排序 — 按创建日期,从最新到最旧。
struct JournalEntryView: View {
@Environment(\.managedObjectContext) var viewContext
@State private var searchText = ""
private var searchPredicate: NSPredicate? {
guard !searchText.isEmpty else { return nil }
return NSPredicate(format: "title CONTAINS[c] %@", searchText)
}
@FetchRequest var entries: FetchedResults<JournalEntry>
init(searchText: String) {
let sort = [NSSortDescriptor(keyPath: \.JournalEntry.date,
ascending: false)]
let pred: NSPredicate? = searchText.isEmpty
? nil
: NSPredicate(format: "title CONTAINS[c] %@ OR content CONTAINS[c] %@",
searchText, searchText)
self._entries = FetchRequest(
sortDescriptors: sort,
predicate: pred,
animation: .default
)
}
var body: some View {
List(entries) { entry in
VStack(alignment: .leading) {
Text(entry.title ?? "").font(.headline)
Text(entry.content ?? "").font(.subheadline)
.lineLimit(2)
}
}
.searchable(text: $searchText)
}
}
带搜索的日记:在 searchable 字段中输入文本时,会在 title 和 content 字段中使用 CONTAINS[c](不区分大小写搜索)创建 NSPredicate。Init 接受 searchText 并使用相应的谓词创建 FetchRequest。Searchable 修改器自动更新 searchText,@FetchRequest 对谓词的更改做出反应并重新请求数据。
对于具有多个过滤器的更复杂场景,将 NSCompoundPredicate 与 AND/OR 逻辑结合使用。@FetchRequest 支持任何复杂度的谓词,包括用于按关系过滤的嵌套子查询 (SUBQUERY)。但请记住:复杂的谓词会影响性能 — 对于大数据集(超过 10,000 条记录),请使用 Core Data 索引并通过 fetchLimit 限制获取。
常见问题
在根 View 中添加 @Environment(\.managedObjectContext) var viewContext,并通过 .environment(\.managedObjectContext, context) 修改器传递它。所有使用 @FetchRequest 的子 View 将自动从环境中获取上下文。
是的,更改 Core Data 上下文:添加、删除或更新对象并调用 save()。@FetchRequest 将自动重新获取并更新 UI。要在不调用 save() 的情况下强制更新,请使用 viewContext.refreshAllObjects()。
@FetchRequest 返回扁平的对象数组。@SectionedFetchRequest 根据指定的 key path 将结果分组为分区,类似于使用 sectionNameKeyPath 的 UITableView。对于带分区标题的分组列表,请使用 @SectionedFetchRequest。
要按关系过滤,请使用点符号:NSPredicate(format: “category.name == %@”, “Work”)。@FetchRequest 在 SQLite 中自动执行 JOIN。要通过所选对象访问关系,请使用常规的 NSManagedObject 属性。
@FetchRequest 不直接支持分页,因为它旨在显示所有记录并带有反应式更新。对于分页,请使用带 fetchLimit/fetchOffset 的 NSFetchRequest,并在滚动列表时手动加载数据。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。