Core-Data 后台保存性能问题
Posted
技术标签:
【中文标题】Core-Data 后台保存性能问题【英文标题】:Core-Data background save performance issues 【发布时间】:2014-03-18 14:14:00 【问题描述】:我使用核心数据编写了一个应用程序,该应用程序通过 wifi 从传感器收集各种数据。我的数据线程在后台运行,从 wifi 的套接字读取数据并创建新的核心数据实体。问题是我每秒大约有 27 次更新,当我试图让线程在收到对象后立即调用保存对象时,我的 UI 开始滞后并且程序变得无法使用我不确定是否这是由于我的代码中的设计缺陷或与 Core Data 的工作方式有关。
我想知道如何在不影响我的 UI 或任何其他应用程序代码的情况下在后台主动进行保存的一些选项。我曾想过也许有一种方法可以在另一个后台线程或其他东西中每隔几秒就批量保存 500 条记录,但我不确定 a)如何实现这个和 b)如果可能的话。
我正在积极创建对象并调用:
[NSEntityDescription insertNewObjectForEntityForName:@"RPYL" inManagedObjectContext:managedObjectContext];
一旦我完成了数据收集,我会打电话给:
[managedObjectContext save:&error]
【问题讨论】:
你在后台保存吗?另外,您是否有任何代码在主线程上运行以响应保存?如果是这样,也许包括一个你正在做什么的例子,也许它可以被 SO 的人改进。 【参考方案1】:你不能在不影响主线程的情况下在后台保存——SQLite 开发人员的观点是"Threads are evil. Avoid them." 的过时观点。因此,使用 SQLite 涉及到很多互斥。在保存时,永久存储被锁定,无论保存来自何处。如果在此期间有任何其他需要访问商店,那么它必须等待。如果您有受影响的索引,则保存涉及获取受影响表中的每个项目并根据索引列对它们进行排序,因为 SQLite 通过二进制搜索实现索引。因此,成本可以取决于您插入的数量以及您在商店中已有的数量。
在执行线程限制跳转到主队列时,首先尝试智能故障 — 使用 NSFetchRequest
并明确表示您希望事情预先发生故障。然后,您无需再去商店就可以访问它们,从而避免碰到互斥锁。尝试为您的目标设置-com.apple.CoreData.SQLDebug 1
运行,以查看您去商店的频率:如果您没有针对它进行优化,那么我保证它会比您想象的要多。
一般提示也适用:如果您有想要做的工作而不是在用户与应用交互时,则针对运行循环进行调度,而不是直接到主队列上。运行循环切换到跟踪模式和退出跟踪模式,因此您可以将您的工作安排为默认模式,它会自动避免在用户控制下 UI 忙碌时运行。
如果这不能解决问题,并且您的记录在接收时是不可变的,没有复杂的查询,那么请考虑使用 Core Data 作为永久存储,但您自己设计的不可变非托管对象供运行时使用。在后台构建它们并将它们向前传递。
编辑:批量保存很容易——只是不要太频繁地调用保存。一种解决方案是使用 BOOL 指示是否安排了保存;当您想保存时,如果没有安排,请从现在开始使用dispatch_after
安排一个。如果安排了一个,那么什么也不做。如果您不断收到数据,那么更复杂的方案(例如,我上次保存是什么时候,那么多久才能再次保存?)不会给您带来太多收益。
【讨论】:
谢谢,这就是我所害怕的【参考方案2】:您需要注意一些事项,并且可以采取一些措施来提高性能。
设置:
我使用DataController
类来设置并提供对所有核心数据对象(persistentStoreCoordinator 等)的访问。在该类中,我有一个类方法,用于在后台运行 Core Data 操作。它创建一个新的NSManagedObjectContext
(因为我们不应该在线程之间传递NSManagedObjectContext
)并在新的上下文中执行一个块。该方法如下所示:
+ (void)saveDataInBackgroundWithSaveBlock:(void(^)(NSManagedObjectContext *context))saveBlock
completionBlock:(void(^)(void))completionBlock
// nested contexts are broken on ios 5, see http://***.com/questions/11786436/core-data-nested-managed-object-contexts-and-frequent-deadlocks-freezes
// that's why we directly use the persistentStoreCoordinator instead
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// setting the undoManager to nil dramatically improves performance and memory usage
context.undoManager = nil;
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[context setPersistentStoreCoordinator:[[self class] sharedInstance].persistentStoreCoordinator];
// make sure our changes are merged into our main `NSManagedObjectContext`
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:[[[self class] sharedInstance] managedObjectContext]
selector:@selector(mergeChangesFromContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:context];
[context performBlock:^
saveBlock(context);
if ([context hasChanges])
NSError *error;
if ([context save:&error])
NSLog(@"saving in bg successful");
else
NSLog(@"Error bg save: %@", error);
dispatch_async(dispatch_get_main_queue(), ^
completionBlock();
);
];
然后你像这样调用这个方法:
[DataController saveDataInBackgroundWithSaveBlock:^(NSManagedObjectContext *context)
// create NSManagedObjects is the background with the given context
completionBlock:^
// do something on the main thread, for example
// [self.collectionView reloadData];
];
性能:
设置context.undoManager = nil;
对内存使用和性能有很大帮助。此外,不要在创建每个对象后保存。而是定期保存,这也会有所帮助。
其中大部分归功于http://www.cimgf.com/2011/05/04/core-data-and-threads-without-the-headache/。
【讨论】:
你能发布你的数据控制器类吗?以上是关于Core-Data 后台保存性能问题的主要内容,如果未能解决你的问题,请参考以下文章
Core-Data:想要将新的 web xml 内容持久保存到我的数据存储中,而不是替换现有的
在同一个数据库上同时使用 core-data 和基于 sqlite c 的 api