【发布时间】:2015-08-18 22:03:48
【问题描述】:
我有一个 iOS Core Data 性能问题,我还没有完全弄清楚。我的应用程序有一个相对简单的 UICollectionViewController,它具有创建 NSFetchedResultsController 的函数,大致如下:
func loadCollection(collectionID: String) {
let fetchRequest = NSFetchRequest(entityName: "Story")
fetchRequest.predicate = NSPredicate(format: "collectionID == %@ AND wasDeleted == FALSE", collectionID)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "yearMonthDay", ascending: true), NSSortDescriptor(key: "startDate", ascending: true)]
fetchRequest.fetchBatchSize = 100
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: mainObjectContext!,
sectionNameKeyPath: "yearMonth", cacheName: collectionID)
var error: NSError?
self.fetchedResultsController.performFetch(&error)
....
}
我的核心数据堆栈大致如下所示,带有两个托管对象上下文,以改善新的和编辑的“故事”对象的对象保存的 UI 响应时间。这是我在 Marcus Zarra 的 Core Data book 和 blog post 等地方看到的基本模式。
// Variation "A" with two managed object contexts.
lazy var mainObjectContext: NSManagedObjectContext? = {
if let coordinator = self.persistentStoreCoordinator {
self.rootContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
self.rootContext?.persistentStoreCoordinator = coordinator
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.parentContext = self.rootContext
return managedObjectContext
} else {
return nil
}
}()
问题是,当我有两个像上面这样的上下文时,上面的初始 fetchedResultsController.performFetch() 比我只有一个像下面这样的 (.MainQueueConcurrencyType) 上下文时长大约 10 倍。
例如,在 iPad Air 上使用两个上下文和大约 1,700 个对象执行上述提取需要 5 秒以上,但对于如下所示的一个上下文则不到 0.4 秒。
// Variation "B" with one managed object context.
lazy var mainObjectContext: NSManagedObjectContext? = {
if let coordinator = self.persistentStoreCoordinator {
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
} else {
return nil
}
}()
我打开了核心数据日志记录 (-com.apple.CoreData.SQLDebug 1),从日志记录中我可以看到,当我有两个对象上下文时,1,700 个对象中的所有属性都将从 SQL 数据库加载初始获取。但是,如果我只有一个上下文,则最初不会从数据库中加载任何属性。
Log from variation "A" with two object contexts:
2015-08-18 13:31:49.761 Myapp[27646:4374573] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND t0.ZWASDELETED = ?) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE
2015-08-18 13:31:52.093 Myapp[27646:4374573] CoreData: annotation: sql connection fetch time: 2.3314s
2015-08-18 13:31:55.011 Myapp[27646:4374573] CoreData: annotation: total fetch execution time: 5.2502s for 1743 rows.
2015-08-18 13:31:55.016 Myapp[27646:4374573] CoreData: sql: SELECT t0.ZYEARMONTH, COUNT (DISTINCT t0.Z_PK) FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND t0.ZWASDELETED = ?) GROUP BY t0.ZYEARMONTH ORDER BY t0.ZYEARMONTH
2015-08-18 13:31:55.276 Myapp[27646:4374573] CoreData: annotation: sql connection fetch time: 0.2590s
2015-08-18 13:31:55.276 Myapp[27646:4374573] CoreData: annotation: total fetch execution time: 0.2600s for 371 rows.
Log from variation "B" with one object context:
2015-08-18 13:25:19.448 Myapp[27635:4362501] CoreData: sql: SELECT 0, t0.Z_PK FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND t0.ZWASDELETED = ?) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE
2015-08-18 13:25:19.789 Myapp[27635:4362501] CoreData: annotation: sql connection fetch time: 0.3405s
2015-08-18 13:25:19.789 Myapp[27635:4362501] CoreData: annotation: total fetch execution time: 0.3410s for 1743 rows.
2015-08-18 13:25:19.790 Myapp[27635:4362501] CoreData: sql: SELECT t0.ZYEARMONTH, COUNT (DISTINCT t0.Z_PK) FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND t0.ZWASDELETED = ?) GROUP BY t0.ZYEARMONTH ORDER BY t0.ZYEARMONTH
2015-08-18 13:25:19.825 Myapp[27635:4362501] CoreData: annotation: sql connection fetch time: 0.0339s
2015-08-18 13:25:19.825 Myapp[27635:4362501] CoreData: annotation: total fetch execution time: 0.0349s for 371 rows.
所以基于这些结果,我修改了 loadCollection 函数以添加
fetchRequest.includesPropertyValues = false
现在有了两个对象上下文,我得到了以下结果,初始加载时间回到了 0.4 秒以下 - 太棒了!:
2015-08-18 13:53:43.345 Myapp[27692:4414951] CoreData: sql: SELECT 0, t0.Z_PK FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND t0.ZWASDELETED = ?) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE
2015-08-18 13:53:43.683 Myapp[27692:4414951] CoreData: annotation: sql connection fetch time: 0.3378s
2015-08-18 13:53:43.684 Myapp[27692:4414951] CoreData: annotation: total fetch execution time: 0.3383s for 1743 rows.
2015-08-18 13:53:43.688 Myapp[27692:4414951] CoreData: sql: SELECT t0.ZYEARMONTH, COUNT (DISTINCT t0.Z_PK) FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND t0.ZWASDELETED = ?) GROUP BY t0.ZYEARMONTH ORDER BY t0.ZYEARMONTH
2015-08-18 13:53:43.722 Myapp[27692:4414951] CoreData: annotation: sql connection fetch time: 0.0329s
2015-08-18 13:53:43.722 Myapp[27692:4414951] CoreData: annotation: total fetch execution time: 0.0339s for 371 rows.
但是,现在为 NSFetchedResultsController 获取的每个对象都是错误的,并导致单行 SQL 查询,这会显着减慢集合视图的滚动速度:
2015-08-18 13:53:44.078 Myapp[27692:4414951] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE t0.Z_PK = ?
2015-08-18 13:53:44.081 Myapp[27692:4414951] CoreData: annotation: sql connection fetch time: 0.0011s
2015-08-18 13:53:44.081 Myapp[27692:4414951] CoreData: annotation: total fetch execution time: 0.0029s for 1 rows.
2015-08-18 13:53:44.082 Myapp[27692:4414951] CoreData: annotation: fault fulfilled from database for : 0xd00000001ad8000c <x-coredata://967F5940-D73C-48E2-A26D-E60FF87BA9F7/Story/p1718>
而不是像这样使用单个上下文和 includePropertyValues 默认为 true 的 100 个左右的批次:
2015-08-18 14:47:44.221 Myapp[27792:4515252] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE t0.Z_PK IN (SELECT * FROM _Z_intarray0) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE LIMIT 100
2015-08-18 14:47:44.468 Myapp[27792:4515252] CoreData: annotation: sql connection fetch time: 0.0959s
2015-08-18 14:47:44.469 Myapp[27792:4515252] CoreData: annotation: total fetch execution time: 0.2566s for 100 rows.
2015-08-18 14:47:44.550 Myapp[27792:4515252] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE LIMIT 100
2015-08-18 14:47:44.614 Myapp[27792:4515252] CoreData: annotation: sql connection fetch time: 0.0067s
2015-08-18 14:47:44.614 Myapp[27792:4515252] CoreData: annotation: total fetch execution time: 0.0635s for 44 rows.
任何关于更好的方法的想法或建议将不胜感激。
【问题讨论】:
-
由于我还没有收到任何建议,我向苹果开发者技术支持提出了这个问题。他们回答说:“不幸的是,嵌套上下文不支持批量获取,因此设置 fetchBatchSize 在该配置中不起作用。您可以为此提交增强请求。”那好吧。但随后他们继续说:“为了让我们提供进一步的帮助,您能否详细说明您将如何保存新的和已编辑的对象,以及为什么一个单独的上下文或连接到一个持久存储协调器的两个上下文对您不起作用?”嗯……