我正在开发一个连接到web服务的应用程序,该应用程序在应用程序启动期间检索大量数据。我使用并发来避免UI阻塞。我选择了以下核心数据堆栈模式:后台专用moc-->主moc-->编写器专用moc-->协调器-->文件。
导入操作时会出现此问题。CPU使用率为100%,应用程序在使用过程中速度变慢。我处理了300个对象的批,总共导入了大约10000个对象。
对于每个批次,都会创建一个NSOperation,其中包含一个关联的临时moc,即后台moc的子级。操作在NSOperationQueue中排队。导入作业完成后,应用程序会变得更慢,具体取决于运行的作业数量。我还注意到,当应用程序被终止并重新启动时,它的可用性和速度确实要高得多。
导入时,我的内存占用在40Mo和60Mo之间变化。你觉得太多了吗?
你认为我的堆叠模式适合我的需要吗?我应该迁移到具有2个协调器的堆栈吗?
此外,当获取数据以在tableView中显示时,我应该在显示视图之前立即使用performBlockAndWait获取数据吗?
感谢您的帮助
所描述的堆栈很好。
CPU使用可能会产生误导。你要确保你不在主线程上,因为这会导致你在应用程序中的大部分速度缓慢和/或断断续续。
当您在Instruments中观看应用程序时,什么最耗时?在主队列上花费了多少时间?
一般来说,导入不应该导致CPU处于100%。如果您在后台线程中执行此操作,则很可能会获得一些性能提升。
如果应该分享你的导入代码和/或仪器跟踪,这样我就可以看到发生了什么。
我认为您的设置有问题。您声明后台托管对象上下文的子线程是主线程,并创建要导入的子线程。这必然会导致UI故障。
此外,我认为依赖NSOperation
是不必要的。您应该使用NSManagedObjectContext
块API。
我建议的设置是:
RootContext (background, writing to persistent store) -> parent of
MainContext (foreground, UI) -> parent of
WorkerContext(s) (background, created and discarded ad hoc)
您可以在web调用的回调中创建辅助上下文,以完成导入的繁重工作。确保您使用的是块API,并将所有对象限制在本地上下文中。save
上下文将更改推送到主线程(在将数据保存到存储之前,主线程已经开始显示数据),并定期保存主上下文和编写器上下文,始终使用bock API。
一个典型的saveContext
函数,可以称为线程安全(这里self
指的是数据管理器单例或应用程序委托):
func saveContext () {
if self.managedObjectContext.hasChanges {
self.managedObjectContext.performBlocAndWait {
do { try self.managedObjectContext.save() }
catch let error as NSError {
print("Unresolved error while saving main context (error), (error.userInfo)")
}
}
self.rootContext.performBlockAndWait {
do { try self.rootContext.save() }
catch let error as NSError {
print("Unresolved error while saving to persistent store (error), (error.userInfo)")
}
}
}
}
经过几天的测试和仪器跟踪,我可以给你更多的细节。以下片段显示了如何从共享实例中保存上下文(基于父/子模式):
- (void)save {
[self.backgroundManagedObjectContext performBlockAndWait:^{
[self saveContext:self.backgroundManagedObjectContext];
[self.mainManagedObjectContext performBlock:^{
[self saveContext:self.mainManagedObjectContext];
[self.writerManagedObjectContext performBlock:^{
[self saveContext:self.writerManagedObjectContext];
}];
}];
}];
}
- (void)saveContext:(NSManagedObjectContext*)context {
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}
}
然后,由于同步操作,每个导入作业都在后台上下文中执行。以下方法在操作主中激发。
- (void)operationDidStart
{
NSManagedObjectContext *moc = self.context;
NSMutableArray *insertedOrUpdatedObjects = [NSMutableArray array];
NSMutableArray *subJSONs = [NSMutableArray array];
NSUInteger numberOfJobs = ceil((double)self.JSONToImport.count/self.batchSize);
for (int i = 0; i < numberOfJobs; i++) {
NSUInteger startIndex = i * self.batchSize;
NSUInteger count = MIN(self.JSONToImport.count - startIndex, self.batchSize);
NSArray *arrayRange = [self.JSONToImport subarrayWithRange:NSMakeRange(startIndex, count)];
[subJSONs addObject:arrayRange];
}
__block NSUInteger numberOfEndedJobs = 0;
for (NSArray *subJSON in subJSONs) {
[moc performBlock:^{
[self startJobWithJSON:subJSON context:moc completion:^(NSArray *importedObjects, NSError *error) {
numberOfEndedJobs++;
if (!error && importedObjects && importedObjects.count > 0) {
[insertedOrUpdatedObjects addObjectsFromArray:importedObjects];
}
if (numberOfEndedJobs == numberOfJobs) {
[[CoreDataManager manager] save];
if (self.operationCompletion) {
self.operationCompletion(self, insertedOrUpdatedObjects, error);
}
}
}];
}];
}
}
正如你所看到的,我把我的进口分成批次(500个)。操作在后台上下文队列上执行每个批处理,并在所有批处理结束时保存堆栈。
由于时间档案器,保存方法似乎为每个线程占用了23%的CPU使用量。
希望尽可能清楚。