NSFetchRequest 是 Core Data 中用于从持久化存储中获取数据的请求对象。它允许定义查询条件(如过滤条件、排序规则等),然后执行查询来检索需要的数据。可以看作是向 Core Data 发出的 “请求” 或 “查询”,它告诉 Core Data 应该从数据存储中提取哪些数据。
主要功能和用法
1、定义查询条件:可以通过 NSFetchRequest 来指定查询的实体类型(数据模型中的对象),以及可能的排序、过滤等条件。
2、执行查询:执行请求后,将获得一个结果集,通常是一个对象数组([Entity] 类型)。
主要参数
1、Entity:要查询的数据实体。
2、Predicate:可选的过滤条件,类似 SQL 中的 WHERE 子句。
3、Sort Descriptors:可选的排序规则,类似 SQL 中的 ORDER BY 子句。
示例
假设我们有一个 ExchangeRate 实体,我们希望获取所有的汇率数据,并按照某个属性(例如汇率)升序排序:
let context = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<ExchangeRate> = ExchangeRate.fetchRequest()
// 添加过滤条件(可选)
fetchRequest.predicate = NSPredicate(format: "currency == %@", "USD")
// 添加排序规则(可选)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "rate", ascending: true)]
do {
let results = try context.fetch(fetchRequest)
print(results) // 处理结果
} catch {
print("获取数据失败: \(error)")
}
核心部分
1、Entity:即查询的对象类型。ExchangeRate.fetchRequest() 创建了一个针对 ExchangeRate 实体的请求。
2、Predicate:过滤条件,用于指定要查询的记录。例如,currency == ‘USD’ 可以筛选出所有 “USD” 货币的汇率数据。
3、Sort Descriptors:定义返回结果的排序方式,例如按汇率升序或降序排序。
常见形式
NSFetchRequest<NSManagedObject>是最常用的获取实体对象(NSManagedObject)的方式。
查询结果会返回完整的 NSManagedObject 实体,可以读取所有属性,甚至进行修改、保存。
let fetchRequest: NSFetchRequest<ExchangeRate> = ExchangeRate.fetchRequest()
let results = try context.fetch(fetchRequest)
// results 是 [ExchangeRate],可直接使用对象的属性
除了NSFetchRequest<NSManagedObject>,还有以下几种常用的NSFetchRequest<T>泛型类型:
1、NSFetchRequest<NSDictionary>
let request = NSFetchRequest<NSDictionary>(entityName: "ExchangeRate")
request.resultType = .dictionaryResultType
request.propertiesToFetch = ["currencySymbol"]
适用场景:只读取部分字段、统计、聚合查询、更快更省内存。
2、NSFetchRequest<NSNumber>
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ExchangeRate")
request.resultType = .countResultType
let count = try context.count(for: request)
适用场景:快速统计某个查询条件下的数据总数。
3、NSFetchRequest<NSManagedObjectID>
let request = NSFetchRequest<NSManagedObjectID>(entityName: "ExchangeRate")
request.resultType = .managedObjectIDResultType
适用场景:拿到 ID 后再异步加载实体对象,减少初期内存占用。
4、注意
虽然可以用泛型 NSFetchRequest<T> 声明,但实际返回值的类型是由 resultType 决定的,所以:
如果用 <NSDictionary>,resultType 必须是 .dictionaryResultType。
如果用 <NSManagedObjectID>,必须是 .managedObjectIDResultType。
否则运行时会崩溃或数据不对。
NSFetchRequest<NSManagedObject>的resultType值默认是.managedObjectResultType:
let request: NSFetchRequest<ExchangeRate> = ExchangeRate.fetchRequest()
// 默认是 .managedObjectResultType
let results = try context.fetch(request)
这是最常用的情况,可以省略设置 resultType,因为:
request.resultType == .managedObjectResultType // 默认值
常用属性
NSFetchRequest 是 Core Data 中用来向数据库查询数据的“查询模板”,它有很多可以用来自定义查询行为的属性。
1、entityName:String类型,用于 查询的实体名。
2、predicate:NSPredicate?类型,用于筛选数据(相当于 SQL 的 WHERE)。
3、sortDescriptors:[NSSortDescriptor]?类型,用于排序规则。
4、fetchLimit:Int类型,限制返回的最大数量(类似 SQL 的 LIMIT)。
5、fetchOffset:Int类型,跳过多少条再开始取(用于分页)。
6、returnsObjectsAsFaults:Bool类型,是否返回“轻量”对象(懒加载,默认 true)。
7、includesPropertyValues:Bool类型,是否包含属性值,默认 true。
8、includesSubentities:Bool类型,是否包含子实体,默认 true。
9、resultType:NSFetchRequestResultType类型,决定返回结果的类型(对象、字典、ID、数量)。
10、propertiesToFetch:[Any]?类型,指定要查询的属性字段(只在 .dictionaryResultType 有效)。
11、returnsDistinctResults:Bool类型,是否去重(配合 propertiesToFetch 使用)。
12、includesPendingChanges:Bool类型,是否包含未保存的更改,默认 true。
13、shouldRefreshRefetchedObjects:Bool类型,是否强制刷新已存在的对象。
常用属性示例
1、筛选 + 排序
let request: NSFetchRequest<ExchangeRate> = ExchangeRate.fetchRequest()
request.predicate = NSPredicate(format: "currencySymbol == %@", "USD")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
2、分页查询
request.fetchLimit = 50
request.fetchOffset = 100 // 从第 101 条开始
3、查询字段 + 去重(dictionary 查询)
let request = NSFetchRequest<NSDictionary>(entityName: "ExchangeRate")
request.resultType = .dictionaryResultType
request.propertiesToFetch = ["currencySymbol"]
request.returnsDistinctResults = true
4、只查数量(不返回具体对象)
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ExchangeRate")
request.resultType = .countResultType
总结
NSFetchRequest 是 Core Data 中获取数据的基本工具,通过它,可以非常灵活地指定查询条件、排序方式和其他限制,从数据存储中提取出所需要的数据。
扩展知识
NSFetchRequest和@FetchRequest的区别
第一种方式:手动使用 NSFetchRequest 和 persistentContainer
static func fetchExchangeRates() -> [ExchangeRate]? {
let context = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<ExchangeRate> = ExchangeRate.fetchRequest()
do {
let results = try context.fetch(fetchRequest)
return results
} catch {
print("获取数据失败: \(error)")
return nil
}
}
这种方法是更传统的 Core Data 使用方式。手动创建一个 NSFetchRequest,然后通过 persistentContainer.viewContext(即 Core Data 的上下文)来执行这个请求。context.fetch(fetchRequest) 会返回请求的数据(ExchangeRate 类型的数组)。这种方式拥有更多控制权,可以自定义请求的内容,例如排序、过滤等。
第二种方式:使用 @FetchRequest 属性包装器
@FetchRequest(
entity: ExchangeRate.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \ExchangeRate.someProperty, ascending: true)]
)
var tasks: FetchedResults<ExchangeRate>
这是一种 SwiftUI 风格的写法,简化了很多事情。通过 @FetchRequest,可以将 NSFetchRequest 和 Core Data 的数据获取过程与视图绑定起来。这个属性包装器会自动在视图更新时重新加载数据,简化了在视图中获取数据的流程。
entity: ExchangeRate.entity():指定了想要获取的实体类型。
sortDescriptors:可以指定排序条件,类似于在手动 NSFetchRequest 中使用 sortDescriptors。
var tasks: FetchedResults<ExchangeRate>:这是绑定的变量,Core Data 会在数据变动时自动更新这个变量,触发视图更新。
为什么看起来不一样?
1、@FetchRequest 是针对 SwiftUI 的:它是为了简化与视图的绑定设计的,适合在视图模型中直接使用,不需要手动管理上下文和数据请求。
2、NSFetchRequest 是传统 Core Data 的方式:适合在更复杂的逻辑中使用,或者当不需要和 UI 直接绑定时。
总结
@FetchRequest:适用于 SwiftUI,直接与视图绑定,自动更新数据。
NSFetchRequest:传统的 Core Data 请求方式,适合更细粒度的控制和非 SwiftUI 环境。
如果做一个 iOS 应用,使用 @FetchRequest 和 SwiftUI 的集成更加流畅,但如果需要更多的控制或者在非视图层次(如数据管理类)操作数据,手动使用 NSFetchRequest 会更适合。
Core Data在dictionaryResultType的类型保护机制
在使用NSFetchRequest<NSDictionary>获取数据的过程中,例如获取date字段,在使用的过程中需要进行解包,例如:
func fetchExistingDates() -> Set<Date> {
let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "Eurofxrefhist")
fetchRequest.resultType = .dictionaryResultType
fetchRequest.propertiesToFetch = ["date"] // 指定 date 字段
fetchRequest.returnsDistinctResults = true // 去重
do {
let results = try context.fetch(fetchRequest)
let dates = results.compactMap { $0["date"] as? Date }
return Set(dates)
} catch {
print("获取已有日期失败: \(error)")
return []
}
}
在这段代码中,当 fetchRequest.resultType 设置为 .dictionaryResultType 时,Core Data 不返回实体对象(NSManagedObject),它返回的是一个个 NSDictionary,里面的字段是以 Any 类型包装的。
因此,需要进行解包:
results.compactMap { $0["date"] as? Date }
在使用的过程中,告诉编译器date应该是Date类型。
如果不进行解包,得到的就是[Any]类型数组,没法直接用于Set<Date>,会报错:
Argument type '[Any]' expected to be an instance of a class or class-constrained type
相关文章
SwiftUI获取Core Data数据的@FetchRequest:https://fangjunyu.com/2025/03/30/swiftui获取core-data数据的fetchrequest/