我在申请中使用Grand Central Dispatch(GCD)来做一些繁重的工作。该应用程序使用Core-Data进行数据存储。这是我的场景(以及相关问题):
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
dispatch_async(request_queue, ^{
MyNSManagedObject *mObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// …
// <heavy lifting>
// …
// …
// <update mObject>
// …
[self saveManagedObjectContext];
});
由于[self saveManagedObjectContext]
,fetchResultsController
委托方法会自动调用。因此,UI更新逻辑启动。
现在我的问题是,我是否需要main_queue
使用-saveManagedObjectContext
?我应该在NSManagedObject
的{{1}}上执行所有操作吗?更新main_queue
的某些操作可能需要2-3秒。请指教。
答案 0 :(得分:60)
核心数据有一个黄金法则 - 每个线程一个托管对象上下文。托管对象上下文不是线程安全的,因此如果您在后台任务中工作,您可以使用主线程来避免与UI操作的线程冲突,或者创建一个新的上下文来完成工作。如果工作要采取几秒钟后你应该做后者来阻止你的UI锁定。
为此,您需要创建一个新的上下文,并为其提供与主上下文相同的持久性存储:
NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
执行您需要执行的任何操作,然后在保存新上下文时,您需要处理保存通知并使用mergeChangesFromContextDidSaveNotification:
消息将更改合并到主上下文中。代码看起来像这样:
/* Save notification handler for the background context */
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we're on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
/* ... */
/* Save the background context and handle the save notification */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
[backgroundContext save:NULL];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:syncContext];
处理保存通知和合并非常重要,否则您的主UI /上下文将无法看到您所做的更改。通过合并,您的主fetchResultsController等将获得更改事件并按预期更新您的UI。
另一个需要注意的重要事项是NSManagedObject实例只能在从中获取它们的上下文中使用。如果您的操作需要对对象的引用,则必须将对象的objectID
传递给操作,并使用existingObjectWithID:
从新上下文中重新获取NSManagedObject实例。如下所示:
/* This can only be used in operations on the main context */
MyNSManagedObject *objectInMainContext =
[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
/* This can now be used in your background context */
MyNSManagedObject *objectInBackgroundContext =
(MyNSManagedObject *) [backgroundContext existingObjectWithID:[objectInMainContext objectID]];
答案 1 :(得分:17)
您可能知道或注意到您必须在主线程上执行UI操作。正如您所提到的那样,当您保存UI更新时。您可以通过在主线程上嵌套对dispatch_sync
的调用来解决此问题。
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
__block __typeof__(self) blockSelf = self;
dispatch_async(request_queue, ^{
MyNSManagedObject *mObject = [blockSelf.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// update and heavy lifting...
dispatch_sync(main_queue, ^{
[blockSelf saveManagedObjectContext];
});
});
使用 blockSelf
是为了避免造成意外的引用周期。(Practical blocks)
答案 2 :(得分:0)
由于Core Data每个线程需要一个托管对象上下文,因此可能的解决方案是在全局管理器中跟踪每个线程的上下文,然后跟踪保存通知并传播到所有线程:
假设:
@property (nonatomic, strong) NSDictionary* threadsDictionary;
以下是如何获取托管对象(每个线程):
- (NSManagedObjectContext *) managedObjectContextForThread {
// Per thread, give one back
NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];
NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
if (existingContext==nil){
existingContext = [[NSManagedObjectContext alloc] init];
[existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
[self.threadsDictionary setValue:existingContext forKey:threadName];
}
return existingContext;
}
在全局管理器的init方法中的某个时刻(我使用了单例):
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
然后接收保存通知并传播到所有其他托管上下文对象:
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we're on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
[context mergeChangesFromContextDidSaveNotification:notification];
}
}
(为清楚起见,删除了其他一些方法)