22

我在我的應用程序中使用Grand Central Dispatch(GCD)來完成一些繁重工作。該應用程序正在使用Core-Data進行數據存儲。這裏是我的方案(連同相關的問題):Grand Central Dispatch(GCD)和CoreData

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?我是否應該對NSManagedObjectmain_queue執行所有操作?某些更新NSManagedObject的操作可能需要2-3秒。請指教。

回答

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

59

當涉及到核心數據 - 每個線程有一個託管對象上下文時,有一條黃金法則。被管對象上下文不是線程安全的,所以如果你在後臺任務中工作,你可以使用主線程來避免線程與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]; 

處理保存notifcation和合並是很重要的,否則你的主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

所以,你說的是,在我的情況下,而不是使用fetchedResultsController,我應該創建一個新的託管對象上下文(backgro如何獲取所需的託管對象,執行所需操作,更新託管對象,保存託管對象上下文(backgroundManagedObjectContext),然後合併更改以反映在主託管對象上下文中?這會讓我的生活非常痛苦。 – Mustafa 2010-11-24 08:54:07

0

由於核心數據需要一個管理的每個線程對象上下文,一個可能的解決方案來跟蹤每線程上下文的全球經理,然後跟蹤保存通知,並傳播到所有線程:

假設:

@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]; 
    } 
} 

(爲了清楚起見,刪除了其他一些方法)

相關問題