CoreData 多線程下NSManagedObjectContext的使用,nsmanagedobject
在Google的時候,我發現了這樣兩篇老外的部落格(,)。前者是介紹NSManagedObjectContext在多線程下的三種設計,後者是博主對這三種設計進行的效能測試。下面我將一一介紹:1. persistentStoreCoordinator<-mainContext<-privateContext 這種設計就是我之前在項目中使用的,也是阻塞UI線程最嚴重的一種設計。它總共有兩個Context,一個是UI線程中使用的mainContext,一個是子線程中使用的privateContext,他們的關係是privateContext.parentContext = mainContext,而mainContext是與Disk串連的Context,所以這種設計下,每當子線程privateContext進行save操作以後,它會將資料庫所有變動Push up到其父Context,也就是mainContext中去,注意:這時子線程的save操作並沒有任何關於Disk IO的操作。而後mainContext在UI線程又要執行一次save操作才能真正將資料變動寫進資料庫中,這裡的save操作就與Disk IO有關了,而且又是在主線程,所以說這種設計是最阻礙UI線程的。2. persistentStoreCoordinator<-backgroundContext<-mainContext<-privateContext 這種設計是第一種的改進設計,也是上述的老外博主推薦的一種設計方式。它總共有三個Context,一是串連persistentStoreCoordinator也是最底層的backgroundContext,二是UI線程的mainContext,三是子線程的privateContext,後兩個Context在1中已經介紹過了,這裡就不再具體介紹,他們的關係是privateContext.parentContext = mainContext, mainContext.parentContext = backgroundContext。下面說說它的具體工作流程。 在應用中,如果我們有API操作,首先我們會起一個子線程進行API請求,在得到Response後要進行資料庫操作,這是我們要建立一個privateContext進行資料的增刪改查,然後call privateContext的save方法進行儲存,這裡的save操作只是將所有資料變動Push up到它的父Context中也就是mainContext中,然後mainContext繼續call save方法,將資料變動Push up到它的父Context中也就是backgroundContext,最後調用backgroundContext的save方法真正將資料變動儲存到Disk資料庫中,在這個過程中,前兩個save操作相對耗時較少,真正耗時的操作是最後backgroundContext的save操作,因為只有它有Disk IO的操作。3. persistentStoreCoordinator<-mainContext persistentStoreCoordinator<-privateContext 第三種設計是最直觀的一種設計,無論是mainContext還是privateContext都是串連persistentStoreCoordinator的。這種設計的工作流程是: 首先在ViewController中要添加一個名為NSManagedObjectContextDidSaveNotification的通知,然後子線程中建立privateContext,進行資料增刪改查操作,直接save到本機資料庫,這時在ViewController中會回調之前註冊的NSManagedObjectContextDidSaveNotification的回調方法,在該方法中調用mainContext的mergeChangesFromContextDidSaveNotification:notification方法,將所有的資料變動merge到mainContext中,這樣就保持了兩個Context中的資料同步。由於大部分的操作都是privateContext在子線程中操作的,所以這種設計是UI線程耗時最少的一種設計,但是它的代價是需要多寫mergeChanges的方法。(註:前兩種parent,child的Context,一旦childContext調用save方法,其parentContext不用任何merge操作,CoreData自動將資料merge到parentContext當中)總結: 第一種設計是失敗的設計,完全可以不考慮,第二種設計比較複雜繁瑣,但是它是最方便而且UI線程的阻塞時間也是相對較少的一種。第三種設計是最少阻塞UI的一種,但是這種設計操作比較繁瑣,應用場合是資料量比較大的應用,一般會應用在公司專屬應用程式中,所以如果你不是企業級的應用或者不是資料量很大的應用,我還是推薦第二種設計。我根據第二種設計寫了一個Demo,下面附上一些關鍵代碼供大家參考: 註:在參考代碼之前大家可能要對NSManagedObjectContext的concurrencyType有所瞭解,還有就是performBlock和performBlockAndWait:方法的區別。//這是appDelegate中的backgroundContext -(NSManagedObjectContext *)rootObjectContext { if (nil != _rootObjectContext) { return _rootObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _rootObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [_rootObjectContext setPersistentStoreCoordinator:coordinator]; } return _rootObjectContext; }//這是mainContext - (NSManagedObjectContext *)managedObjectContext { if (nil != _managedObjectContext) { return _managedObjectContext; } _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; _managedObjectContext.parentContext = [self rootObjectContext]; return _managedObjectContext; // _managedObjectContext = [[NSManagedObjectContext alloc] init]; // _managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator]; // return _managedObjectContext; }//AppDelegate中saveContext方法,每次privateContext調用save方法成功之後都要call這個方法 - (void)saveContextWithWait:(BOOL)needWait { NSManagedObjectContext *managedObjectContext = [self managedObjectContext]; NSManagedObjectContext *rootObjectContext = [self rootObjectContext]; if (nil == managedObjectContext) { return; } if ([managedObjectContext hasChanges]) { NSLog(@"Main context need to save"); [managedObjectContext performBlockAndWait:^{ NSError *error = nil; if (![managedObjectContext save:&error]) { NSLog(@"Save main context failed and error is %@", error); } }]; } if (nil == rootObjectContext) { return; } RootContextSave rootContextSave = ^ { NSError *error = nil; if (![_rootObjectContext save:&error]) { NSLog(@"Save root context failed and error is %@", error); } }; if ([rootObjectContext hasChanges]) { NSLog(@"Root context need to save"); if (needWait) { [rootObjectContext performBlockAndWait:rootContextSave]; } else { [rootObjectContext performBlock:rootContextSave]; } } }//這是偽API方法,僅供Demo使用 +(void)getEmployeesWithMainContext:(NSManagedObjectContext *)mainContext completionBlock:(CompletionBlock)block { NSManagedObjectContext *workContext = [NSManagedObjectContext generatePrivateContextWithParent:mainContext]; [workContext performBlock:^{ Employee *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:workContext]; [employee setRandomData]; NSError *error = nil; if([workContext save:&error]) { block(YES, nil, nil); } else { NSLog(@"Save employee failed and error is %@", error); block(NO, nil, @"Get emploree failed"); } }]; }//這是NSManagedObjectContext的Category @implementation NSManagedObjectContext (GenerateContext) +(NSManagedObjectContext *)generatePrivateContextWithParent:(NSManagedObjectContext *)parentContext { NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; privateContext.parentContext = parentContext; return privateContext; } +(NSManagedObjectContext *)generateStraightPrivateContextWithParent:(NSManagedObjectContext *)mainContext { NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] init]; privateContext.persistentStoreCoordinator = mainContext.persistentStoreCoordinator; return privateContext; } @end//這是ViewController裡API操作和UI重新整理的相關代碼,從refreshData方法 -(void)refreshData { [EmployeeTool getEmployeesWithMainContext:[self mainContext] completionBlock:^(BOOL operationSuccess, id responseObject, NSString *errorMessage) { if ([NSThread isMainThread]) { NSLog(@"Handle result is main thread"); [self handleResult:operationSuccess]; } else { NSLog(@"Handle result is other thread"); [self performSelectorOnMainThread:@selector(handleResult:) withObject:[NSNumber numberWithBool:operationSuccess] waitUntilDone:YES]; } }]; } -(void)handleResult:(BOOL)operationSuccess { if (operationSuccess) { NSLog(@"Operation success"); [self saveContext]; } [self.refreshControl endRefreshing]; } -(void)saveContext { WMAppDelegate *appDelegate = (WMAppDelegate*)[UIApplication sharedApplication].delegate; [appDelegate saveContextWithWait:NO]; }