SwiftUI — FetchRequest

SwiftUI 的 @FetchRequest 属性包装器是与 Core Data 深度集成的关键工具,用于在视图中声明式地获取并监听数据变化。以下是其核心要点及用法介绍:


基本概念 #

  • 用途:从 Core Data 中获取数据,并在数据变化时自动更新视图。
  • 自动同步:当 Core Data 的上下文(NSManagedObjectContext)发生变动时,@FetchRequest 会自动刷新关联的视图。
  • 声明式语法:通过属性包装器配置请求参数(如实体、排序、过滤条件),简化数据获取逻辑。

核心配置参数 #

  1. 实体(Entity):指定要查询的 Core Data 实体类型(如 Item.self)。
  2. 排序描述符(SortDescriptors):定义结果的排序规则(如按日期升序)。
  3. 谓词(Predicate):设置过滤条件(如只查询未完成的任务)。
  4. 动画(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 默认优化了数据加载,但可通过 fetchLimitfetchBatchSize 控制数据量。
  • 惰性加载FetchedResults 是惰性加载的,适合搭配 ListLazyVStack 使用。
@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 宏)和声明式查询。
  • 动态更新方法
    1. 传递参数初始化:通过父视图传递条件参数,如:
      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) })
          }
      }
      
    2. 响应状态变化

方案二:使用 @FetchRequest(Core Data) #

  • 适用场景:传统 Core Data 项目,需直接操作 NSPredicate
  • 动态更新方法:直接修改 nsPredicate 属性(如您之前提供的代码示例)。

3. 关键差异总结 #

特性@Query(SwiftData)@FetchRequest(Core Data)
框架依赖SwiftData(iOS 17+)Core Data(全版本兼容)
语法声明式,基于 Swift 宏 @Model需手动定义 NSManagedObject 模型
动态条件更新直接修改 filtersort修改 nsPredicatesortDescriptors
性能优化内置关系懒加载、批量操作需手动优化 Fetch 请求

4. 如何避免混淆? #

  1. 检查框架依赖:若代码中使用了 @Model 宏定义模型,则为 SwiftData,应使用 @Query;若使用 NSManagedObject,则为 Core Data。
  2. 查看官方文档:SwiftData 的 @Query 文档明确其动态查询能力,而 Core Data 的 @FetchRequest 需通过谓词修改实现动态更新。
本文共 1731 字,创建于 Mar 11, 2025
相关标签: Xcode, SwiftUI