2011-10-11 47 views
2

我已仔細閱讀了SO上的所有相關線程,但仍然對如何更改多線程核心數據對象而不必保存更改。如何在不必保存每次更改後對不同線程中的Core Data對象進行更改

我正在研究一個不斷與服務器通話的應用程序。該應用程序使用Core Data進行存儲,並在幾個視圖控制器中使用NSFetchedResultsController來從持久存儲中獲取數據。通常當用戶執行一個動作時,會觸發一個網絡請求。在發送網絡請求之前,通常應對相關的核心數據對象進行一些更改,並在服務器響應時對這些核心數據對象進行更多更改。

最初所有的核心數據操作都在同一個NSManagedObjectContext的主線程上完成。這一切都很好,除了當網絡流量很高時,應用程序可能會在幾秒鐘內無響應。顯然這是不可接受的,所以我考慮將一些Core Data操作移到後臺運行。

我嘗試的第一種方法是創建一個NSOperation對象來處理每個網絡響應。在NSOperation對象的主要方法內部,我設置了一個專用的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的核心數據對象。每次更改通常都很小(例如,更新對象中的一個實例變量),但可能會有很多變化。所以我真的不想在每次更改後保存主要的MOC。但是如果我不這樣做,我會在將後臺MOC的變更合併到主要MOC時遇到問題。由於兩個MOC都有未保存的更改,因此會發生合併衝突。設置合併策略也沒有幫助,因爲我想保留兩個MOC的更改。

一種可能性是將背景MOC與NSManagedObjectContextDidSaveNotification一起註冊,但這種方法對我來說就像是糟糕的設計。在每次更改後,我仍然需要保存主要的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]; 
} 

所以每當我需要從主線程核心數據的變化,我不得不從主線程獲得的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。

我覺得我失去了一些東西。我真的希望有人能夠對此有所瞭解。謝謝。

回答

2

一個NSManagedObjectContext只是一個便箋。保存它的行爲將一個便箋簿中的更改移動到NSPersistentStoreCoordinator,並可能下移到磁盤。一個MOC可以通過NSPersistentStoreCoordinator得知另一個MOC的變化。因此需要保存。但是,在下一個iOS版本中,節省成本很低。

如果你必須iOS4的兼容或小於儲蓄是唯一的選擇。但是,根據應用程序的設計,您可以加載保存並降低頻率。如果您要導入數據,請在導入完成時保存,或者在導入中保存爲邏輯單元。每次進入後都不需要保存,這很浪費。

順便說一下,我會建議使用NSOperation實例,而不是直接與NSThread實例一起使用。他們更容易合作並且表現更好。

而且,你並不需要在try/catch塊包裹Objective-C代碼。很少有事情會拋出異常;特別是在iOS上。

最後,我建議考慮看看my post on CIMGF有關導入在後臺線程。

+0

嗨馬庫斯,我通過你的崗位去了,在我的應用程序也做了類似的方法,但有時當它試圖合併的變化,我不知道如何避免因爲合併的變化是在Mainthread發生我的UI是反應遲鈍幾秒鐘 –

+0

合併到主線程將導致主線程在合併期間阻塞。避免這種情況(iOS 5之前)的方法是保存更小的塊,以便主線程有時間做其他事情。你想保持簡短的節省,這樣它們就不是人類可以感知的。 –

+0

是...現在我的保存操作在15個記錄中有超過1000條記錄,我觀察到UI無響應性得到改善,但仍然存在秒數分割,我想我會嘗試使用每個塊較少的記錄來檢查它是否改善.. –

相關問題