在 SwiftUI 中将 .fetch 批量大小与 @FetchRequest 结合使用
Use .fetchBatchSize with @FetchRequest in SwifUI
这几天我一直在努力理解和弄明白,但仍然想不出解决办法。
例如,有一个包含 100 万行的核心数据实体,我想在一个视图中显示所有记录,并带有“滚动时加载”以提高性能,因为加载 100 万行加载会很慢而且很浪费。
根据文档,基本上 .fetchBatchSize
只会在需要时加载 'batches' 数据,这是解决问题的完美方法。用户滚动列表,SwiftUI 在需要时批量加载数据。
这是我的(简体):
ContextView.swift
struct ContentView: View {
@FetchRequest private var items: FetchedResults<Item>
init() {
_items = FetchRequest<Item>(fetchRequest: request())
}
var body: some View {
List(items) {
Text([=10=].name)
}
}
func request() -> NSFetchRequest<Item> {
let request = Item.fetchRequest()
request.sortDescriptors = []
request.fetchBatchSize = 5
return request
}
}
问题:@FetchRequest 同时加载所有记录而不进行批处理 and/or 它确实有效但同时检索所有批次并击败批量检索的全部目的。
我尝试实际加载 100 万行,但需要花费大量时间来显示视图(因为它正在检索和准备所有 100 万行数据)。如果“.fetchBatchSize”有效,它将仅加载前 5 个,并且会随着列表滚动而缓慢加载。
*注意:我知道有 .fetchLimit
& .fetchOffset
,但它需要实现一个单独的逻辑 *
您不能使用 fetchBatchSize
,因为 List
需要 SwiftUI diff 检测插入、删除和移动的所有 objectID
。这就是为什么如果您设置的批处理大小比没有批处理慢得多,它会加载所有批处理。
但是您可以禁用 includesPropertyValues 以防止 Core Data 将所有数据加载到其行缓存中。
禁用后 SQL 查询变为:
SELECT 0, t0.Z_PK FROM ZITEM t0
而不是在获取所有字段的地方默认启用:
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZTIMESTAMP FROM ZITEM t0
要查看查询,请使用启动参数 -com.apple.CoreData.SQLDebug 4
(数字越大,日志输出越多)。
您还可以通过将 [=20=].name
移动到子视图的主体中来利用 List 的惰性行为,以便核心数据仅访问数据库以读取数据并在它滚动到屏幕上,您会在日志中看到:
CoreData: details: SQLite: EXPLAIN QUERY PLAN SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP, t0.ZTITLE FROM ZITEM t0 WHERE t0.Z_PK = ?
2 0 0 SEARCH t0 USING INTEGER PRIMARY KEY (rowid=?)
CoreData: annotation: fault fulfilled from database for : 0xa17723cef738c1a0 <x-coredata://FA6F121D-805F-4558-AAAC-F5F60A117256/Item/p9778> with row values: <NSSQLRow: 0x600001745b60>{Item 1-9778-1 timestamp=2022-02-24 13:58:39 +0000 title=NULL and to-manys=0x0}
这些改进可能会带来足够大的性能提升,看起来像这样:
extension Item {
static func myFetchRequest() -> NSFetchRequest<Item> {
let fr = Self.fetchRequest()
fr.includesPropertyValues = false
fr.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]
return fr
}
}
struct MyView: View {
@ObservedObject var item: Item
var body: some View {
Text(item.timestamp!, formatter: itemFormatter)
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
fetchRequest: Item.myFetchRequest(),
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
MyView(item: item)
...
如果您使用@SectionedFetchRequest,那么您将需要获取分段中使用的字段,这可以通过保留 includesPropertyValues
其默认值 true 并将 propertiesToFetch
设置为数组来完成属性 用于分段的名称。
例如
extension Item {
static func myFetchRequest() -> NSFetchRequest<Item> {
let fr = Self.fetchRequest()
fr.propertiesToFetch = ["title"]
fr.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]
return fr
}
}
初始查询结果:
CoreData: sql: SELECT 1, t0.Z_PK, t0.ZTITLE FROM ZITEM t0 ORDER BY t0.ZTIMESTAMP
如您所见,select timestamp
并非如此。当列表滚动并访问对象时,会执行其他查询以检索所有字段。
这几天我一直在努力理解和弄明白,但仍然想不出解决办法。
例如,有一个包含 100 万行的核心数据实体,我想在一个视图中显示所有记录,并带有“滚动时加载”以提高性能,因为加载 100 万行加载会很慢而且很浪费。
根据文档,基本上 .fetchBatchSize
只会在需要时加载 'batches' 数据,这是解决问题的完美方法。用户滚动列表,SwiftUI 在需要时批量加载数据。
这是我的(简体):
ContextView.swift
struct ContentView: View {
@FetchRequest private var items: FetchedResults<Item>
init() {
_items = FetchRequest<Item>(fetchRequest: request())
}
var body: some View {
List(items) {
Text([=10=].name)
}
}
func request() -> NSFetchRequest<Item> {
let request = Item.fetchRequest()
request.sortDescriptors = []
request.fetchBatchSize = 5
return request
}
}
问题:@FetchRequest 同时加载所有记录而不进行批处理 and/or 它确实有效但同时检索所有批次并击败批量检索的全部目的。
我尝试实际加载 100 万行,但需要花费大量时间来显示视图(因为它正在检索和准备所有 100 万行数据)。如果“.fetchBatchSize”有效,它将仅加载前 5 个,并且会随着列表滚动而缓慢加载。
*注意:我知道有 .fetchLimit
& .fetchOffset
,但它需要实现一个单独的逻辑 *
您不能使用 fetchBatchSize
,因为 List
需要 SwiftUI diff 检测插入、删除和移动的所有 objectID
。这就是为什么如果您设置的批处理大小比没有批处理慢得多,它会加载所有批处理。
但是您可以禁用 includesPropertyValues 以防止 Core Data 将所有数据加载到其行缓存中。
禁用后 SQL 查询变为:
SELECT 0, t0.Z_PK FROM ZITEM t0
而不是在获取所有字段的地方默认启用:
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZTIMESTAMP FROM ZITEM t0
要查看查询,请使用启动参数 -com.apple.CoreData.SQLDebug 4
(数字越大,日志输出越多)。
您还可以通过将 [=20=].name
移动到子视图的主体中来利用 List 的惰性行为,以便核心数据仅访问数据库以读取数据并在它滚动到屏幕上,您会在日志中看到:
CoreData: details: SQLite: EXPLAIN QUERY PLAN SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP, t0.ZTITLE FROM ZITEM t0 WHERE t0.Z_PK = ?
2 0 0 SEARCH t0 USING INTEGER PRIMARY KEY (rowid=?)
CoreData: annotation: fault fulfilled from database for : 0xa17723cef738c1a0 <x-coredata://FA6F121D-805F-4558-AAAC-F5F60A117256/Item/p9778> with row values: <NSSQLRow: 0x600001745b60>{Item 1-9778-1 timestamp=2022-02-24 13:58:39 +0000 title=NULL and to-manys=0x0}
这些改进可能会带来足够大的性能提升,看起来像这样:
extension Item {
static func myFetchRequest() -> NSFetchRequest<Item> {
let fr = Self.fetchRequest()
fr.includesPropertyValues = false
fr.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]
return fr
}
}
struct MyView: View {
@ObservedObject var item: Item
var body: some View {
Text(item.timestamp!, formatter: itemFormatter)
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
fetchRequest: Item.myFetchRequest(),
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
MyView(item: item)
...
如果您使用@SectionedFetchRequest,那么您将需要获取分段中使用的字段,这可以通过保留 includesPropertyValues
其默认值 true 并将 propertiesToFetch
设置为数组来完成属性 用于分段的名称。
例如
extension Item {
static func myFetchRequest() -> NSFetchRequest<Item> {
let fr = Self.fetchRequest()
fr.propertiesToFetch = ["title"]
fr.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]
return fr
}
}
初始查询结果:
CoreData: sql: SELECT 1, t0.Z_PK, t0.ZTITLE FROM ZITEM t0 ORDER BY t0.ZTIMESTAMP
如您所见,select timestamp
并非如此。当列表滚动并访问对象时,会执行其他查询以检索所有字段。