2010-08-13 18 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

相關問題