我已仔細閱讀了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。
我覺得我失去了一些東西。我真的希望有人能夠對此有所瞭解。謝謝。
嗨馬庫斯,我通過你的崗位去了,在我的應用程序也做了類似的方法,但有時當它試圖合併的變化,我不知道如何避免因爲合併的變化是在Mainthread發生我的UI是反應遲鈍幾秒鐘 –
合併到主線程將導致主線程在合併期間阻塞。避免這種情況(iOS 5之前)的方法是保存更小的塊,以便主線程有時間做其他事情。你想保持簡短的節省,這樣它們就不是人類可以感知的。 –
是...現在我的保存操作在15個記錄中有超過1000條記錄,我觀察到UI無響應性得到改善,但仍然存在秒數分割,我想我會嘗試使用每個塊較少的記錄來檢查它是否改善.. –