我在这里阅读了很多关于NSManagedObjectContext和多线程应用程序的帖子。我还讨论了CoreDataBooks示例,以了解单独的线程如何需要自己的NSManagedObjectContext,以及保存操作如何与主NSManagedObjectContext合并。我发现这个例子很好,但也是应用程序特定的。我试图概括这一点,并想知道我的方法是否合理。
我的方法是使用泛型函数来获取当前线程的NSManagedObjectContext。该函数返回主线程的NSManagedObjectContext,但如果从另一个线程中调用,则会创建一个新线程(或从缓存中获取它)。具体如下:
+(NSManagedObjectContext *)managedObjectContext {
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:@"%p", thread];
// delegate.managedObjectContexts is a mutable dictionary in the app delegate
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
if ( [managedObjectContexts objectForKey:threadKey] == nil ) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] autorelease];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
// cache the context for this thread
[managedObjectContexts setObject:threadContext forKey:threadKey];
}
return [managedObjectContexts objectForKey:threadKey];
}
如果从主线程调用,保存操作很简单。从其他线程调用的保存操作需要在主线程内合并。为此,我有一个通用的commit
函数:
+(void)commit {
// get the moc for this thread
NSManagedObjectContext *moc = [self managedObjectContext];
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
NSError *error;
if (![moc save:&error]) {
// fail
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
在contextDidSave:
函数中,如果commit
中的通知调用,我们会执行合并。
+(void)contextDidSave:(NSNotification*)saveNotification {
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
[moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:YES];
}
最后,我们使用以下方法清理NSManagedObjectContext的缓存:
+(void)initialize {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(threadExit)
name:NSThreadWillExitNotification
object:nil];
}
+(void)threadExit {
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
[managedObjectContexts removeObjectForKey:threadKey];
}
这编译并且似乎有效,但我知道由于竞争条件,线程问题可能很棘手。有人看到这种方法有问题吗?
另外,我在异步请求(使用ASIHTTPRequest)的上下文中使用它,它从服务器获取一些数据并更新并在iPhone上插入商店。似乎NSThreadWillExitNotification在请求完成后不会触发,然后将相同的线程用于后续请求。这意味着相同的NSManagedObjectContext用于同一线程上的单独请求。这是一个问题吗?
答案 0 :(得分:8)
发布此问题一年后,我终于构建了一个框架来概括和简化我使用Core Data的工作。它超越了原始问题,并添加了许多功能,使Core Data交互变得更加容易。详情请见https://github.com/chriscdn/RHManagedObject
答案 1 :(得分:0)
我在最终更好地理解问题后找到了解决方案。我的解决方案没有直接解决上面的问题,但确实解决了为什么我必须首先处理线程的问题。
我的应用程序使用ASIHTTPRequest库进行异步请求。我从服务器获取一些数据,并使用委托requestFinished
函数来添加/修改/删除我的核心数据对象。 requestFinished
函数在不同的线程中运行,我认为这是异步请求的自然副作用。
深入挖掘之后,我发现ASIHTTPRequest故意在一个单独的线程中运行请求,但可以在我的ASIHTTPRequest子类中重写:
+(NSThread *)threadForRequest:(ASIHTTPRequest *)request {
return [NSThread mainThread];
}
这个小小的改动将requestFinished
放在主线程中,这消除了我在应用程序中关心线程的需要。