通常情況下轮锥,CoreData 的增刪改查操作都在主線程上執(zhí)行击蹲,那么對數(shù)據(jù)庫的操作就會影響到 UI 操作纽绍,這在操作的數(shù)據(jù)量比較小的時候蕾久,執(zhí)行的速度很快,我們也不會察覺到對 UI 的影響顶岸,但是當(dāng)數(shù)據(jù)量特別大的時候腔彰,再把 CoreData 的操作放到主線程中就會影響到 UI 的流暢性。自然而然地我們就會想到使用后臺線程來處理大量的數(shù)據(jù)操作辖佣。
使用后臺 managedObjectContext
CoreData 里使用后臺更新數(shù)據(jù)最常用的方案是一個 persistentStoreCoordinator 持久化存儲協(xié)調(diào)器對應(yīng)兩個 managedObjectContext 管理上下文霹抛,NSManagedObjectContext 在創(chuàng)建時,可以傳入 ConcurrencyType 來指定 context 的并發(fā)類型卷谈。指定 NSMainQueueConcurrencyType 就是我們平時創(chuàng)建的運行在主隊列的 context杯拐;指定成 NSPrivateQueueConcurrencyType 的話,context 就會運行在它所管理的一個私有隊列中世蔗;另外還有 NSConfinementConcurrencyType 是適用于舊設(shè)備的并發(fā)類型端逼,現(xiàn)在已經(jīng)被廢棄了,所以實際上只有兩種并發(fā)類型
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
在最新的 iOS 10 中污淋,CoreData 棧的創(chuàng)建被封裝在了 NSPersistentContainer 類中顶滩,用它來創(chuàng)建 backgroundContext 更加簡單
NSManagedObjectContext *backgroundContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).persistentContainer.newBackgroundContext;
另外,后臺 context 的操作得放在 performBlock 或 performBlockAndWait 方法里執(zhí)行寸爆,performBlock 會異步的執(zhí)行礁鲁,不會阻塞當(dāng)前的線程盐欺,而 performBlockAndWait 則會阻塞當(dāng)前的線程直到方法返回才會繼續(xù)向下執(zhí)行。下面是一段后臺插入數(shù)據(jù)的示例代碼:
AppDelegate中在程序?qū)⒁M入后臺時發(fā)送通知
- (void)applicationWillResignActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:@"willResignActive" object:nil];
}
控制器中接收通知并處理相應(yīng)的事件
- (void)viewDidLoad {
[super viewDidLoad];
//APP將要進入后臺
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive) name:@"willResignActive" object:nil];
//后臺數(shù)據(jù)變更后通知主隊列做數(shù)據(jù)合并
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveContextSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundContext];
}
- (void)appWillResignActive {
//后臺做數(shù)據(jù)操作
[self.backgroundContext performBlock:^{
for (NSUInteger i = 0; i < 1000; i++) {
NSString *name = [NSString stringWithFormat:@"student-%d", arc4random_uniform(9999)];
int16_t age = arc4random_uniform(10) + 10;
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.backgroundContext];
student.name = name;
student.age = age;
}
NSError *error;
[self.backgroundContext save:&error];
}];
}
后臺插入數(shù)據(jù)之后仅醇,還沒有完冗美,因為數(shù)據(jù)是通過后臺的 context 寫入到本地的持久化數(shù)據(jù)庫的,所以這時候主隊列的 context 是不知道本地數(shù)據(jù)變化的析二,所以還需要通知到主隊列的 context:“數(shù)據(jù)庫的內(nèi)容有變化啦粉洼,看看你有沒有需要合并的”。這個過程可以通過監(jiān)聽一條通知來實現(xiàn)叶摄。這個通知就是 NSManagedObjectContextDidSaveNotification属韧,在每次調(diào)用 NSManagedObjectContext 的 save:方法時都會自動發(fā)送,通知中的 userInfo 中包含了修改的數(shù)據(jù)准谚,可以通過 NSInsertedObjectsKey挫剑、NSUpdatedObjectsKey去扣、 NSDeletedObjectsKey 這三個 key 獲取到柱衔。收到通知之后,只需要調(diào)用 [self.mainContext mergeChangesFromContextDidSaveNotification:note] 就可以將修改的數(shù)據(jù)合并到主線程的 context愉棱。
- (void)receiveContextSave:(NSNotification *)notification {
//收到通知之后唆铐,只需要調(diào)用 [self.mainContext mergeChangesFromContextDidSaveNotification:note] 就可以將修改的數(shù)據(jù)合并到主線程的 context
[self.context mergeChangesFromContextDidSaveNotification:notification];
NSLog(@"notification===%@",notification);
//通知的 userInfo 里保存的 managedObjects 不可以直接在另一個線程的 context 中直接使用!也就是 managedObject 不是跨線程的奔滑,如果想要在別的線程操作艾岂,必須通過 objectId 在另一個 context 里再重新獲得這個 object
NSSet<Student *> *managedObjects = notification.userInfo[NSInsertedObjectsKey];
NSManagedObjectID *studentId = managedObjects.allObjects[0].objectID;
[self.context performBlock:^{
// 這是錯的
// Student *wrongStudent = managedObjects.allObjects[0];
// 應(yīng)該這么做
Student *student = [self.context objectWithID:studentId];
NSLog(@"name===%@",student.name);
}];
}