【发布时间】:2011-10-11 18:24:58
【问题描述】:
我仔细阅读了 SO 上的所有相关线程,但仍然对如何从多个线程更改核心数据对象而无需在每次更改后进行保存感到困惑。
我正在开发一个不断与服务器对话的应用程序。该应用程序使用 Core Data 进行存储,NSFetchedResultsController 在一些视图控制器中用于从持久存储中获取数据。通常当用户执行一个动作时,会触发一个网络请求。在发送网络请求之前,通常应该对相关的 Core Data 对象进行一些更改,而在服务器响应时,会对这些 Core Data 对象进行更多的更改。
最初所有的核心数据操作都是在同一个NSManagedObjectContext 的主线程上完成的。一切都很好,只是当网络流量很高时,应用程序可能会在几秒钟内变得无响应。显然这是不可接受的,所以我考虑将一些 Core Data 操作移动到后台运行。
我尝试的第一种方法是创建一个 NSOperation 对象来处理每个网络响应。在 NSOperation 对象的 main 方法中,我设置了一个专门的 MOC,进行一些更改,最后提交更改。
- (void)main
{
@try {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Create a dedicated MOC for this NSOperation
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[APP_DELEGATE persistentStoreCoordinator]];
// Make change to Core Data objects
// ...
// Commit the changes
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}
// Release the MOC
[context release];
// Drain the pool
[pool drain];
}
@catch (NSException *exception) {
// Important that we don't rethrow exception here
NSLog(@"Exception: %@", exception);
}
}
主线程上的MOC注册为NSManagedObjectContextDidSaveNotification。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
所以当后台上下文提交更改时,会通知主 MOC,然后将更改合并:
- (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 the changes into the main context
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
但是,正如我之前提到的,我还需要从主 MOC 对 Core Data 对象进行更改。每个更改通常非常小(例如更新对象中的一个实例变量),但可能有很多。所以我真的不想在每次更改后都保存主 MOC。但是,如果我不这样做,我会在将更改从后台 MOC 合并到主 MOC 时遇到问题。由于两个 MOC 都有未保存的更改,因此会发生合并冲突。设置合并策略也无济于事,因为我想保留两个 MOC 的更改。
一种可能性是也使用NSManagedObjectContextDidSaveNotification 注册后台MOC,但这种方法对我来说听起来像是糟糕的设计。而且我仍然需要在每次更改后保存主 MOC。
我尝试的第二种方法是从在永久后台线程上运行的专用后台上下文中进行所有核心数据更改。
- (NSThread *)backgroundThread
{
if (backgroundThread_ == nil) {
backgroundThread_ = [[NSThread alloc] initWithTarget:self selector:@selector(backgroundThreadMain) object:nil];
// Actually start the thread
[backgroundThread_ start];
}
return backgroundThread_;
}
// Entry point of the background thread
- (void)backgroundThreadMain
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// We can't run the runloop unless it has an associated input source or a timer, so we'll just create a timer that will never fire.
[NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:@selector(ignore) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
// Create a dedicated NSManagedObjectContext for this thread.
backgroundContext_ = [[NSManagedObjectContext alloc] init];
[backgroundContext_ setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
[pool drain];
}
因此,每当我需要从主线程更改 Core Data 时,我必须从主线程获取 objectID,并传递给后台线程来执行更改。当后台上下文保存时,更改将被合并回主 MOC。
- (void)addProduct:(Product *)product toCatalog:(Catalog *)catalog;
将更改为:
- (void)addProduct:(NSManagedObjectID *)productObjectId toCatalog:(NSManagedObjectID *)catalogObjectId
{
NSArray * params = [NSArray dictionaryWithObjects:productObjectId, catalogObjectId, nil];
[self performSelector:(addProductToCatalogInBackground:) onThread:backgroundThread_ withObject:params waitUntilDone:NO];
}
但这看起来如此复杂和丑陋。编写这样的代码似乎首先否定了使用 Core Data 的用处。此外,我仍然需要在每次更改后保存 MOC,因为如果不先将新对象保存到数据存储区,我无法获取新对象的 objectId。
我觉得我在这里遗漏了一些东西。我真的希望有人能对此有所了解。谢谢。
【问题讨论】:
标签: iphone multithreading core-data