SwiftUI 的 @FetchRequest
属性包装器是与 Core Data 深度集成的关键工具,用于在视图中声明式地获取并监听数据变化。以下是其核心要点及用法介绍:
基本概念 #
- 用途:从 Core Data 中获取数据,并在数据变化时自动更新视图。
- 自动同步:当 Core Data 的上下文(
NSManagedObjectContext
)发生变动时,@FetchRequest
会自动刷新关联的视图。 - 声明式语法:通过属性包装器配置请求参数(如实体、排序、过滤条件),简化数据获取逻辑。
核心配置参数 #
- 实体(Entity):指定要查询的 Core Data 实体类型(如
Item.self
)。 - 排序描述符(SortDescriptors):定义结果的排序规则(如按日期升序)。
- 谓词(Predicate):设置过滤条件(如只查询未完成的任务)。
- 动画(Animation):数据变化时的视图过渡动画(默认使用
.default
)。
@FetchRequest(
sortDescriptors: [SortDescriptor(\.timestamp, order: .reverse)],
predicate: NSPredicate(format: "isCompleted == %@", NSNumber(value: false)),
animation: .default
)
private var items: FetchedResults<Item>
动态调整请求 #
通过初始化时绑定到状态变量,实现动态过滤或排序。例如,根据用户输入实时更新查询条件:
struct TodoListView: View {
@State private var searchText = ""
var body: some View {
List {
ForEach(items) { item in
Text(item.title ?? "")
}
}
.searchable(text: $searchText)
}
// 动态 FetchRequest,根据 searchText 更新谓词
private var items: FetchRequest<Item>
init(searchText: String) {
let predicate = searchText.isEmpty
? NSPredicate(value: true)
: NSPredicate(format: "title CONTAINS[c] %@", searchText)
_items = FetchRequest(
sortDescriptors: [SortDescriptor(\.timestamp)],
predicate: predicate,
animation: .default
)
}
}
性能优化 #
- 批量获取:Core Data 默认优化了数据加载,但可通过
fetchLimit
和fetchBatchSize
控制数据量。 - 惰性加载:
FetchedResults
是惰性加载的,适合搭配List
或LazyVStack
使用。
@FetchRequest(
sortDescriptors: [SortDescriptor(\.timestamp)],
fetchLimit: 50,
fetchBatchSize: 20
)
private var items: FetchedResults<Item>
与 SwiftUI 生命周期集成 #
- 自动管理:
@FetchRequest
在视图加载时自动激活,在视图销毁时停止监听。 - 上下文依赖:需确保环境中有正确的
managedObjectContext
(通常通过@Environment(\.managedObjectContext)
注入)。
预览支持 #
在 Xcode 预览中提供测试数据:
struct TodoListView_Previews: PreviewProvider {
static var previews: some View {
TodoListView()
.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
示例代码 #
显示一个简单的任务列表:
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var context
@FetchRequest(
entity: Item.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: false)],
animation: .default
)
private var items: FetchedResults<Item>
var body: some View {
List {
ForEach(items) { item in
Text(item.title ?? "Untitled")
}
.onDelete(perform: deleteItems)
}
.toolbar {
Button("Add") {
let newItem = Item(context: context)
newItem.title = "New Task"
newItem.timestamp = Date()
try? context.save()
}
}
}
private func deleteItems(offsets: IndexSet) {
offsets.map { items[$0] }.forEach(context.delete)
try? context.save()
}
}
注意事项 #
- 主线程限制:Core Data 操作需在主线程执行,
@FetchRequest
自动处理了这一要求。 - 数据更新:修改数据后需调用
context.save()
以持久化更改,@FetchRequest
会自动响应变更。 - 复杂查询:对大数据集或复杂查询,考虑在后台线程执行操作(如
NSManagedObjectContext.performBackgroundTask
)。
通过 @FetchRequest
,SwiftUI 将数据驱动 UI 的理念与 Core Data 无缝结合,显著简化了数据绑定的复杂度。
与 Query 区别 #
1. 框架归属与核心区别 #
@FetchRequest
:属于 Core Data 框架,用于在 SwiftUI 中直接绑定 Core Data 的查询请求到视图。@Query
:属于 SwiftData 框架(苹果推出的新一代数据管理框架),用于声明式查询 SwiftData 模型数据。
两者的底层实现和语法特性不同,不能混用。例如:
- Core Data 示例(动态更新
@FetchRequest
):@FetchRequest(sortDescriptors: [SortDescriptor(\.name)]) private var items: FetchedResults<Item> // 动态更新谓词 items.nsPredicate = NSPredicate(format: "category == %@", selectedCategory)
- SwiftData 示例(动态更新
@Query
):@Query(sort: \.name) private var items: [Item] // 动态更新查询条件 items.filter = #Predicate { $0.category == selectedCategory }
2. 如何正确选择动态查询方案? #
方案一:使用 @Query
(SwiftData)
#
- 适用场景:基于 SwiftData 的现代项目,需简化模型定义(通过
@Model
宏)和声明式查询。 - 动态更新方法:
- 传递参数初始化:通过父视图传递条件参数,如:
struct FilteredMovieList: View { @Query(filter: #Predicate<Movie> { $0.title.contains(searchText) }) private var movies: [Movie] init(searchText: String) { _movies = Query(filter: #Predicate { $0.title.contains(searchText) }) } }
- 响应状态变化
- 传递参数初始化:通过父视图传递条件参数,如:
方案二:使用 @FetchRequest
(Core Data)
#
- 适用场景:传统 Core Data 项目,需直接操作
NSPredicate
。 - 动态更新方法:直接修改
nsPredicate
属性(如您之前提供的代码示例)。
3. 关键差异总结 #
特性 | @Query (SwiftData) | @FetchRequest (Core Data) |
---|---|---|
框架依赖 | SwiftData(iOS 17+) | Core Data(全版本兼容) |
语法 | 声明式,基于 Swift 宏 @Model | 需手动定义 NSManagedObject 模型 |
动态条件更新 | 直接修改 filter 或 sort | 修改 nsPredicate 或 sortDescriptors |
性能优化 | 内置关系懒加载、批量操作 | 需手动优化 Fetch 请求 |
4. 如何避免混淆? #
- 检查框架依赖:若代码中使用了
@Model
宏定义模型,则为 SwiftData,应使用@Query
;若使用NSManagedObject
,则为 Core Data。 - 查看官方文档:SwiftData 的
@Query
文档明确其动态查询能力,而 Core Data 的@FetchRequest
需通过谓词修改实现动态更新。