@FetchRequest 在 SwiftUI 中 — 是什么,Core Data 获取及机制

作者: IT Sectr 发布日期: 2026-06-25 阅读时间: 8 分钟

@FetchRequest 在 SwiftUI 中 — 是一个用于从 Core Data 自动获取数据并反应式更新界面的 property wrapper。该 wrapper 接受带有过滤谓词和排序的 NSFetchRequest,结果 — NSManagedObject 数组 — 会在 Core Data 上下文中的任何更改时自动更新。根据 Apple Developer Documentation (2025)@FetchRequest 需要在 SwiftUI 环境中提供 managedObjectContext,并支持动态谓词,在 Core Data 和用户界面之间提供反应式连接。

要点

  • @FetchRequest — 在 SwiftUI 中用于反应式获取 Core Data 的 property wrapper
  • 自动更新 UI — 在 Core Data 中添加、删除或修改记录时
  • 谓词和排序在初始化器中或通过 property wrapper 参数设置
  • managedObjectContext 必须通过 @Environment 在 View 层级中可用
  • 动态谓词允许在运行时更改过滤条件

SwiftUI 中的 @FetchRequest 是什么?

@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 的排序和谓词

@FetchRequest 接受多个参数来配置获取。SortDescriptors — 确定记录顺序的 NSSortDescriptor 数组。Predicate — 用于过滤的 NSPredicate,例如仅获取状态为“已完成”的任务。Animation — 数据变化时更新列表的动画。

Core Data 谓词使用类似 SQL 的表达式格式:NSPredicate(format: “status == %@”, “completed”)。多个条件可以通过 AND、OR 和 NOT 组合。SortDescriptors 确定顺序:NSSortDescriptor(keyPath: \Task.dueDate, ascending: true)。要按多个字段排序,传递描述符数组 — 第一个作为主要排序键,其余作为次要排序键。

swift
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 和动态数据

@FetchRequest 与 List 和 ForEach 完美结合,用于显示动态 Core Data 列表。获取结果 (FetchedResults) 符合 RandomAccessCollection 协议,因此可以直接在 List 和 ForEach 中使用。在添加、删除或修改记录时,列表会自动以动画方式重绘。

删除记录使用上下文的 delete 方法。添加 — 创建新的 NSManagedObject 并调用 save。所有更改通过 viewContext 执行,viewContext 与 @FetchRequest 一起确保数据一致性。对于编辑,对特定的 Core Data 对象使用 @ObservedObject 或 @Bindable。

swift
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 获取记录的完整日记应用程序示例。搜索表单使用动态谓词:输入文本时,FetchRequest 自动更新结果,按标题和内容过滤记录。排序 — 按创建日期,从最新到最旧。

swift
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 限制获取。

常见问题

如何将 managedObjectContext 传递给 @FetchRequest?

在根 View 中添加 @Environment(\.managedObjectContext) var viewContext,并通过 .environment(\.managedObjectContext, context) 修改器传递它。所有使用 @FetchRequest 的子 View 将自动从环境中获取上下文。

能否在不重新加载 View 的情况下更新 @FetchRequest?

是的,更改 Core Data 上下文:添加、删除或更新对象并调用 save()。@FetchRequest 将自动重新获取并更新 UI。要在不调用 save() 的情况下强制更新,请使用 viewContext.refreshAllObjects()。

@FetchRequest 和 @SectionedFetchRequest 有什么区别?

@FetchRequest 返回扁平的对象数组。@SectionedFetchRequest 根据指定的 key path 将结果分组为分区,类似于使用 sectionNameKeyPath 的 UITableView。对于带分区标题的分组列表,请使用 @SectionedFetchRequest。

如何将 @FetchRequest 与 Relationship 一起使用?

要按关系过滤,请使用点符号:NSPredicate(format: “category.name == %@”, “Work”)。@FetchRequest 在 SQLite 中自动执行 JOIN。要通过所选对象访问关系,请使用常规的 NSManagedObject 属性。

@FetchRequest 是否支持分页?

@FetchRequest 不直接支持分页,因为它旨在显示所有记录并带有反应式更新。对于分页,请使用带 fetchLimit/fetchOffset 的 NSFetchRequest,并在滚动列表时手动加载数据。

总结

  • @FetchRequest — 在 SwiftUI 中用于自动反应式获取 Core Data 的 property wrapper
  • 排序通过 NSSortDescriptor 数组设置,过滤 — 通过 NSPredicate
  • 自动更新 UI — Core Data 上下文中的任何数据更改
  • managedObjectContext 通过 @Environment 在 View 层级中传递
  • 动态谓词通过带谓词参数的自定义 init 实现
  • CRUD 操作通过 viewContext 执行,自动同步列表
  • 使用 @FetchRequest 显示 Core Data,@SectionedFetchRequest — 用于分组列表

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读