2010-08-13 15 views
13

我已经阅读了大量有关NSManagedObjectContext和多线程应用程序的文章。我还浏览了CoreDataBooks示例,了解独立线程如何需要自己的NSManagedObjectContext,以及如何将保存操作与主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]; 
    } 
} 

在我们执行合并,如果commit称为被通知contextDidSave:功能。

+(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

克里斯,我面临着一个类似的多线程问题,同时使用在主线程中创建一个NSManagedObjectContext在NSoperation队列中的所有操作。当每个线程试图保存上下文时,应用程序崩溃会随机抛出核心数据的异常。我正在考虑在所有操作中使用这个上下文,以便每个操作都拥有上下文的独占访问权限。我已经阅读过你的解决方案。你可以粘贴我用于合并上下文的新代码,也可以使用锁来播放游戏 – 2011-05-14 18:36:48

回答

8

发表这个问题一年后,我终于建立了一个框架来概括和简化我与Core Data的合作。它超越了原来的问题,并添加了许多功能,使Core Data交互更加容易。详细信息在这里:https://github.com/chriscdn/RHManagedObject

0

我终于更好地理解问题后找到了解决方案。我的解决方案并不直接解决上述问题,但确实解决了为什么我必须首先处理线程的问题。

我的应用程序使用ASIHTTPRequest库进行异步请求。我从服务器获取一些数据,并使用代理requestFinished函数来添加/修改/删除我的核心数据对象。 requestFinished函数在不同的线程中运行,我认为这是异步请求的自然副作用。

更深的挖掘后,我发现ASIHTTPRequest故意运行在一个单独的线程的请求,但可以在我的ASIHTTPRequest的子类覆盖:

+(NSThread *)threadForRequest:(ASIHTTPRequest *)request { 
    return [NSThread mainThread]; 
} 

这个小变化使requestFinished在主线程中,已消除我需要关心我的应用程序中的线程。

+0

我不太确定我的理解。 ASIHTTPRequest对异步请求使用单独的线程(实际上是一个NSOperationQueue),但同样它总是在mainthread上运行requestFinished。 (在最新的代码中,这个功能在callSelectorOnMainThread方法中。)这就是说我看不到你的解决方案有什么缺点。 – JosephH 2010-08-16 13:07:32

+0

我发现requestFinished没有在主线程上运行,直到我在上面添加了这三行。这是否有可能因为ASIHTTPRequest而改变?我正在使用v1.7。 – chris 2010-08-17 10:40:45

+0

你是对的。 ASIHTTPRequest委托在主线程上运行,但我实现了一个ASIHTTPRequest的子类,并将我的代码放入'requestFinished:'方法中。这并不一定会在主线程中调用。谢谢 – chris 2011-03-04 12:14:06

相关问题