CoreData 從入門到精通(四)併發操作
通常情況下,CoreData 的增刪改查操作都在主執行緒上執行,那麼對資料庫的操作就會影響到 UI 操作,這在操作的資料量比較小的時候,執行的速度很快,我們也不會察覺到對 UI 的影響,但是當資料量特別大的時候,再把 CoreData 的操作放到主執行緒中就會影響到 UI 的流暢性。自然而然地我們就會想到使用後臺執行緒來處理大量的資料操作。
使用後臺 managedObjectContext
CoreData 裡使用後臺更新資料最常用的方案是一個 persistentStoreCoordinator
持久化儲存協調器對應兩個 managedObjectContext
管理上下文,NSManagedObjectContext
ConcurrencyType
來指定 context
的併發型別。指定 NSMainQueueConcurrencyType
就是我們平時建立的執行在主佇列的 context
;指定成 NSPrivateQueueConcurrencyType
的話,context
就會執行在它所管理的一個私有佇列中;另外還有 NSConfinementConcurrencyType
是適用於舊裝置的併發型別,現在已經被廢棄了,所以實際上只有兩種併發型別。 下面是建立
backgroundContext
的程式碼:
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
在最新的 iOS 10 中,CoreData 棧的建立被封裝在了 NSPersistentContainer
類中,用它來建立 backgroundContext
更加簡單:
NSManagedObjectContext *backgroundContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).persistentContainer.newBackgroundContext ;
另外,後臺 context
的操作得放在 performBlock
或 performBlockAndWait
方法裡執行,performBlock
會非同步的執行,不會阻塞當前的執行緒,而 performBlockAndWait
則會阻塞當前的執行緒直到方法返回才會繼續向下執行。下面是一段後臺插入資料的示例程式碼:
[self.backgroundContext performBlock:^{
for (NSUInteger i = 0; i < 100000; i++) {
NSString *name = [NSString stringWithFormat:@"student-%d", arc4random_uniform(9999)];
int16_t age = arc4random_uniform(10) + 10;
int16_t stuId = arc4random_uniform(9999);
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.backgroundContext];
student.studentName = name;
student.studentAge = age;
student.studentId = stuId;
}
NSError *error;
[self.backgroundContext save:&error];
[self.logger dealWithError:error
whenFail:@"failed to insert"
whenSuccess:@"insert success"];
}];
後臺插入資料之後,還沒有完,因為資料是通過後臺的 context
寫入到本地的持久化資料庫的,所以這時候主佇列的 context
是不知道本地資料變化的,所以還需要通知到主佇列的 context
:“資料庫的內容有變化啦,看看你有沒有需要合併的”。這個過程可以通過監聽一條通知來實現。這個通知就是 NSManagedObjectContextDidSaveNotification
,在每次呼叫 NSManagedObjectContext
的 save:
方法時都會自動傳送,通知中的 userInfo
中包含了修改的資料,可以通過 NSInsertedObjectsKey
、NSUpdatedObjectsKey
、 NSDeletedObjectsKey
這三個 key 獲取到。
收到通知之後,只需要呼叫 [self.mainContext mergeChangesFromContextDidSaveNotification:note]
就可以將修改的資料合併到主執行緒的 context
。
下面是示例程式碼:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveContextSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundContext];
}
- (void)doSometingInsertingInBackground {
// backgroundContext ....
}
- (void)receiveContextSave:(NSNotification *)note {
[self.context mergeChangesFromContextDidSaveNotification:note];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
注意:通知的 userInfo
裡儲存的 managedObjects
不可以直接在另一個執行緒的 context
中直接使用!也就是 managedObject
不是跨執行緒的,如果想要在別的執行緒操作,必須通過 objectId
在另一個 context
裡再重新獲得這個 object
。
- (void)receiveContextSave:(NSNotification *)note {
[self.context mergeChangesFromContextDidSaveNotification:note];
NSSet<Student *> *managedObjects = note.userInfo[NSInsertedObjectsKey];
NSManagedObjectID *studentId = managedObjects.allObjects[0].objectID;
[self.context performBlock:^{
// 這是錯的
// Student *wrongStudent = managedObjects.allObjects[0];
// 應該這麼做
Student *student = [self.context objectWithID:studentId];
// modify student...
}];
}