在上篇博客中,講了數(shù)據(jù)模型和 CoreData 棧的創(chuàng)建,那下一步就是對(duì)數(shù)據(jù)的操作了。和數(shù)據(jù)庫(kù)一樣,CoreData 里的操作也無(wú)非是增刪改查。下面我們將逐步講解在 CoreData 中進(jìn)行增刪改查的方式。
基本的增刪改查
插入條目
先來(lái)看一下插入條目的方式,在插入之前,我們需要先創(chuàng)建要插入的數(shù)據(jù), 使用 NSEntityDesctiption 類的 + (__kindof NSManagedObject *)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context; 方法來(lái)創(chuàng)建一個(gè)新的 NSManagedObject 對(duì)象,入?yún)⒎謩e是 entityName 和 managedObjectContext,entityName 也就是實(shí)體類的名字,例如,我要插入一條新的 Student 字段,entityName 就是 @"Student";
context 是 NSManagedObjectContext對(duì)象,新增的實(shí)體類對(duì)象會(huì)添加到對(duì)應(yīng)的 context 上下文對(duì)象中。這個(gè)方法返回的是一個(gè) NSManagedObject 實(shí)例,可以根據(jù)具體情況轉(zhuǎn)換成相應(yīng)的子類:
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
student.studentName = @"小明";
student.studentId = 1;
student.studentAge = 20;
NSError *error;
[self.context save:&error];
調(diào)用 save 方法時(shí),可以傳入一個(gè) NSError 的指針,如果數(shù)據(jù)保存出錯(cuò)的話,錯(cuò)誤信息會(huì)保存到 error 里,這也是 Objective-C 里通常處理錯(cuò)誤的方式。
查詢條目
從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù),會(huì)用到三個(gè)類:NSFetchRequest,NSPredicate,NSSortDescriptor,分別說(shuō)一下這三個(gè)類的作用:
- NSFetchRequest — fetchRequest 代表了一條查詢請(qǐng)求,相當(dāng)于 SQL 中的 SELECT 語(yǔ)句
- NSPredicate — predicate 翻譯過(guò)來(lái)是謂詞的意思,它可以指定一些查詢條件,相當(dāng)于 SQL 中的 WHERE 子句,有關(guān) NSPredicate 的用法,可以看我之前寫(xiě)過(guò)的一篇文章:使用 NSPredicate 進(jìn)行數(shù)據(jù)庫(kù)查詢
- NSSortDescriptor — sortDescriptor 是用來(lái)指定排序規(guī)則的,相當(dāng)于 SQL 中的 ORDER BY 子句
在NSFetchRequest 中有兩個(gè)屬性 predicate、sortDescriptors,就是用來(lái)指定查詢的限制條件的。其中 sortDescriptors 是一個(gè) NSSortDescriptor 的數(shù)組,也就是可以給一個(gè)查詢指定多個(gè)排序規(guī)則,這些排序規(guī)則的優(yōu)先級(jí)就是它們?cè)跀?shù)組中的位置,數(shù)組前面的優(yōu)先級(jí)會(huì)比后面的高。除此之外,NSFetchRequest 還有下面這些屬性
- fetchLimit — 指定結(jié)果集中數(shù)據(jù)的最大條目數(shù),相當(dāng)于 SQL 中的 LIMIT 子句
- fetchOffset — 指定查詢的偏移量,默認(rèn)為 0
- fetchBatchSize — 指定批處理查詢的大小,設(shè)置了這個(gè)屬性后,查詢的結(jié)果集會(huì)分批返回
- entityName/entity — 指定查詢的數(shù)據(jù)表,相當(dāng)于 SQL 中的 FROM 語(yǔ)句
- propertiesToGroupBy — 指定分組規(guī)則,相當(dāng)于 SQL 中的 GROUP BY 子句
- propertiesToFetch — 指定要查詢的字段,默認(rèn)會(huì)查詢?nèi)孔侄?/li>
配置好 NSFetchRequest 對(duì)象后,需要調(diào)用 NSManagedObjectContext 的 - (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error; 來(lái)執(zhí)行查詢,返回的數(shù)組就是查詢出的結(jié)果集。
Xcode 中預(yù)置了用來(lái)創(chuàng)建 fetchRequest 的code snippet,鍵入fetch,就會(huì)出現(xiàn)相應(yīng)的提示:

創(chuàng)建出來(lái)的代碼是這樣子的:

基本上常用到的代碼都在這里了。當(dāng)然我們也可以自己來(lái)手寫(xiě)出來(lái),下面就是一個(gè)最簡(jiǎn)單的查詢語(yǔ)句:
NSFetchRequest *fetchRequest = [Student fetchRequest]; // 自動(dòng)創(chuàng)建的 NSManagedObject 子類里會(huì)生成相應(yīng)的 fetchRequest 方法
// 也可以使用這種方式:[NSFetchRequest fetchRequestWithEntityName:@"Student"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"studentAge > %@", @(20)];
NSArray<NSSortDescriptor *> *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"studentName" ascending:YES]];
fetchRequest.sortDescriptors = sortDescriptors;
//
NSArray<Student *> *students = [self.context executeFetchRequest:fetchRequest error:nil];
刪除條目
簡(jiǎn)單的刪除條目還是比較簡(jiǎn)單的,在上一步里查詢出來(lái)后,只需調(diào)用 NSManagedObjectContext 的 - (void)deleteObject:(NSManagedObject *)object; 方法來(lái)刪除一個(gè)條目。例如,將上面查詢出來(lái)的 students 全部刪除,可以這么寫(xiě):
for (Student *student in students) {
[self.context deleteObject:student];
}
[self.context save:nil]; // 最后不要忘了調(diào)用 save 使操作生效。
更新條目
還是在查詢出的 students 數(shù)組的基礎(chǔ)上,如果要更新里面的字段,可以遍歷這個(gè)數(shù)組,依次修改數(shù)組里元素的字段:
for (Student *student in students) {
student.studentName = @"newName";
}
[self.context save:nil];
增刪改查進(jìn)階
批量插入
簡(jiǎn)單的批量插入和插入單條數(shù)據(jù)一樣,只是在所有的數(shù)據(jù)都插入只有才調(diào)用 [context save]; 來(lái)保存。下面是簡(jiǎn)單的示例代碼:
for (NSUInteger i = 0; i < 1000; i++) {
Student *newStudent = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
int16_t stuId = arc4random_uniform(9999);
newStudent.studentName = [NSString stringWithFormat:@"student-%d", stuId];
newStudent.studentId = stuId;
newStudent.studentAge = arc4random_uniform(10) + 10;
}
[self.context save:nil];
批量更新
這里講的批量更新方式,用到的是集合類型中的 KVC 特性。這是什么呢?就是在 NSArray 這樣的集合類型里,可以調(diào)用它的 [setValue: forKeyPath:] 方法來(lái)更新這個(gè)數(shù)組中所有元素所對(duì)應(yīng)的 keypath。例如想要將上面查詢出來(lái)的 students 數(shù)組里所有元素的 studentName 屬性都修改成 @"anotherName",就可以這么來(lái)寫(xiě):
[students setValue:@"anotherName" forKeyPath:@"studentName"];
除了這種批量更新的方式,還有下面將要講的 NSBatchUpdateRequest 也可以進(jìn)行批量更新,不妨接著往下看。
NSBatchUpdateRequest 批量更新
NSBatchUpdateRequest 是在 iOS 8, macOS 10.10 之后新添加的 API,它是專門(mén)用來(lái)進(jìn)行批量更新的。因?yàn)橛蒙厦婺欠N方式批量更新的話,會(huì)存在一個(gè)問(wèn)題,就是更新前需要將要更新的數(shù)據(jù),查詢出來(lái),加載到內(nèi)存中;這在數(shù)據(jù)量非常大的時(shí)候,假如說(shuō)要更新十萬(wàn)條數(shù)據(jù),就比較麻煩了,因?yàn)閷?duì)于手機(jī)這種內(nèi)存比較小的設(shè)備,直接加載這么多數(shù)據(jù)到內(nèi)存里顯然是不可能的。解決辦法就是每次只查詢出讀取一小部分?jǐn)?shù)據(jù)到內(nèi)存中,然后對(duì)其進(jìn)行更新,更新完之后,再更新下一批,就這樣分批來(lái)處理。但這顯然不是高效的解決方案。
于是就有了 NSBatchUpdateRequest 這個(gè) API。它的工作原理是不加載到內(nèi)存里,而是直接對(duì)本地?cái)?shù)據(jù)庫(kù)中數(shù)據(jù)進(jìn)行更新。這就避免了內(nèi)存不足的問(wèn)題;但同時(shí),由于是直接更新數(shù)據(jù)庫(kù),所以內(nèi)存中的 NSManagedObjectContext 不會(huì)知道數(shù)據(jù)庫(kù)的變化,解決辦法是調(diào)用 NSManagedObjectContext 的 + (void)mergeChangesFromRemoteContextSave:(NSDictionary*)changeNotificationData intoContexts:(NSArray<NSManagedObjectContext*> *)contexts;方法來(lái)告訴 context,有哪些數(shù)據(jù)更新了。
下面來(lái)看一下 NSBatchUpdateRequest 的用法。
-
創(chuàng)建
// 根據(jù) entity 創(chuàng)建 NSBatchUpdateRequest *updateRequest = [[NSBatchRequest alloc] initWithEntity:[Student entity]]; // 根據(jù) entityName 創(chuàng)建 NSBatchUpdateRequest *updateRequest = [[NSBatchUpdateRequest alloc] initWithEntityName:@"Student"]; -
predicate
NSBatchUpdateRequest的predicate用來(lái)指定更新條件,例如這里指定更新 studentAge 等于 20 的學(xué)生updateRequest.predicate = [NSPredicate predicateWithFormat:@"studentAge == %@", @(20)]; -
propertiesToUpdate
propertiesToUpdate屬性是一個(gè)字典,用它來(lái)指定需要更新的字段,字典里的 key 就是要更新的字段名,value 就是要設(shè)置的新值。因?yàn)?Objective-C 字典里只能存儲(chǔ)對(duì)象類型,所以如果字段基本數(shù)據(jù)類型的的話,需要轉(zhuǎn)換成NSNumber對(duì)象。updateRequest.propertiesToUpdate = @{@"studentName" : @"anotherName"}; -
resultType
resultType屬性是NSBatchUpdateRequestResultType類型的枚舉,用來(lái)指定返回的數(shù)據(jù)類型。這個(gè)枚舉有三個(gè)成員:-
NSStatusOnlyResultType— 返回 BOOL 結(jié)果,表示更新是否執(zhí)行成功 -
NSUpdatedObjectIDsResultType— 返回更新成功的對(duì)象的 ID,是 NSArray<NSManagedObjectID *> * 類型。 -
NSUpdatedObjectsCountResultType— 返回更新成功數(shù)據(jù)的總數(shù),是數(shù)字類型
一般我們將其指定為
NSUpdatedObjectIDsResultTypeupdateRequest.resultType = NSUpdatedObjectIDsResultType; -
-
executeRequest
配置完
NSBatchUpdateRequest對(duì)象后,就可以通過(guò) context 的- (nullable __kindof NSPersistentStoreResult *)executeRequest:(NSPersistentStoreRequest*)request error:(NSError **)error;方法來(lái)執(zhí)行批量更新了:NSBatchUpdateResult *updateResult = [self.context executeRequest:updateRequest error:&error]; NSArray<NSManagedObjectID *> *updatedObjectIDs = updateResult.result;executeRequest方法返回的是NSBatchUpdateResult對(duì)象,里面有一個(gè)id result屬性,它的具體類型就是前面通過(guò)枚舉成員指定的類型。 -
mergeChanges
底層數(shù)據(jù)更新之后,現(xiàn)在要通知內(nèi)存中的 context 了,調(diào)用 context 的
mergeChanges方法,第一個(gè)參數(shù)是個(gè)字典,指定要合并的數(shù)據(jù)類型(是更新還是刪除、插入等);第二個(gè)參數(shù)就是 context 數(shù)組,指定要合并到哪些 context 中去。NSDictionary *updatedDict = @{NSUpdatedObjectsKey : updatedObjectIDs}; [NSManagedObjectContext mergeChangesFromRemoteContextSave:updatedDict intoContexts:@[self.context]];
到這里,批量更新的操作就講完了,下面來(lái)看 NSBatchDeleteRequest 批量刪除。
NSBatchDeleteRequest 批量刪除
NSBatchDeleteRequest 的用法和 NSBatchUpdateRequest 很相似,不同的是 NSBatchDeleteRequest 需要指定 fetchRequest 屬性來(lái)進(jìn)行刪除;而且它是 iOS 9 才添加進(jìn)來(lái)的,和 NSBatchUpdateRequest 的適用范圍不一樣,下面看一下示例代碼:
NSFetchRequest *deleteFetch = [Student fetchRequest];
deleteFetch.predicate = [NSPredicate predicateWithFormat:@"studentAge == %@", @(20)];
NSBatchDeleteRequest *deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:deleteFetch];
deleteRequest.resultType = NSBatchDeleteResultTypeObjectIDs;
NSBatchDeleteResult *deleteResult = [self.context executeRequest:deleteRequest error:nil];
NSArray<NSManagedObjectID *> *deletedObjectIDs = deleteResult.result;
NSDictionary *deletedDict = @{NSDeletedObjectsKey : deletedObjectIDs};
[NSManagedObjectContext mergeChangesFromRemoteContextSave:deletedDict intoContexts:@[self.context]];
好了,CoreData 中的增刪改查就講完了,下篇文章將會(huì)介紹 CoreData Model 中的 relationships 實(shí)現(xiàn)多表關(guān)聯(lián)的用法。