在上篇博客中,講了數據模型和 CoreData 棧的創建,那下一步就是對數據的操作了。和數據庫一樣,CoreData 里的操作也無非是增刪改查。下面我們將逐步講解在 CoreData 中進行增刪改查的方式。
基本的增刪改查
插入條目
先來看一下插入條目的方式,在插入之前,我們需要先創建要插入的數據, 使用 NSEntityDesctiption
類的 + (__kindof NSManagedObject *)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;
方法來創建一個新的 NSManagedObject
對象,入參分別是 entityName
和 managedObjectContext
,entityName
也就是實體類的名字,例如,我要插入一條新的 Student
字段,entityName
就是 @"Student";
context
是 NSManagedObjectContext
對象,新增的實體類對象會添加到對應的 context
上下文對象中。這個方法返回的是一個 NSManagedObject
實例,可以根據具體情況轉換成相應的子類:
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
student.studentName = @"小明";
student.studentId = 1;
student.studentAge = 20;
NSError *error;
[self.context save:&error];
調用 save
方法時,可以傳入一個 NSError
的指針,如果數據保存出錯的話,錯誤信息會保存到 error
里,這也是 Objective-C
里通常處理錯誤的方式。
查詢條目
從數據庫中查詢數據,會用到三個類:NSFetchRequest
,NSPredicate
,NSSortDescriptor
,分別說一下這三個類的作用:
- NSFetchRequest — fetchRequest 代表了一條查詢請求,相當于 SQL 中的 SELECT 語句
- NSPredicate — predicate 翻譯過來是謂詞的意思,它可以指定一些查詢條件,相當于 SQL 中的 WHERE 子句,有關 NSPredicate 的用法,可以看我之前寫過的一篇文章:使用 NSPredicate 進行數據庫查詢
- NSSortDescriptor — sortDescriptor 是用來指定排序規則的,相當于 SQL 中的 ORDER BY 子句
在NSFetchRequest
中有兩個屬性 predicate
、sortDescriptors
,就是用來指定查詢的限制條件的。其中 sortDescriptors
是一個 NSSortDescriptor
的數組,也就是可以給一個查詢指定多個排序規則,這些排序規則的優先級就是它們在數組中的位置,數組前面的優先級會比后面的高。除此之外,NSFetchRequest
還有下面這些屬性
- fetchLimit — 指定結果集中數據的最大條目數,相當于 SQL 中的 LIMIT 子句
- fetchOffset — 指定查詢的偏移量,默認為 0
- fetchBatchSize — 指定批處理查詢的大小,設置了這個屬性后,查詢的結果集會分批返回
- entityName/entity — 指定查詢的數據表,相當于 SQL 中的 FROM 語句
- propertiesToGroupBy — 指定分組規則,相當于 SQL 中的 GROUP BY 子句
- propertiesToFetch — 指定要查詢的字段,默認會查詢全部字段
配置好 NSFetchRequest
對象后,需要調用 NSManagedObjectContext
的 - (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error;
來執行查詢,返回的數組就是查詢出的結果集。
Xcode 中預置了用來創建 fetchRequest 的code snippet,鍵入fetch
,就會出現相應的提示:
創建出來的代碼是這樣子的:
基本上常用到的代碼都在這里了。當然我們也可以自己來手寫出來,下面就是一個最簡單的查詢語句:
NSFetchRequest *fetchRequest = [Student fetchRequest]; // 自動創建的 NSManagedObject 子類里會生成相應的 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];
刪除條目
簡單的刪除條目還是比較簡單的,在上一步里查詢出來后,只需調用 NSManagedObjectContext
的 - (void)deleteObject:(NSManagedObject *)object;
方法來刪除一個條目。例如,將上面查詢出來的 students
全部刪除,可以這么寫:
for (Student *student in students) {
[self.context deleteObject:student];
}
[self.context save:nil]; // 最后不要忘了調用 save 使操作生效。
更新條目
還是在查詢出的 students
數組的基礎上,如果要更新里面的字段,可以遍歷這個數組,依次修改數組里元素的字段:
for (Student *student in students) {
student.studentName = @"newName";
}
[self.context save:nil];
增刪改查進階
批量插入
簡單的批量插入和插入單條數據一樣,只是在所有的數據都插入只有才調用 [context save];
來保存。下面是簡單的示例代碼:
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
這樣的集合類型里,可以調用它的 [setValue: forKeyPath:]
方法來更新這個數組中所有元素所對應的 keypath。例如想要將上面查詢出來的 students
數組里所有元素的 studentName
屬性都修改成 @"anotherName"
,就可以這么來寫:
[students setValue:@"anotherName" forKeyPath:@"studentName"];
除了這種批量更新的方式,還有下面將要講的 NSBatchUpdateRequest
也可以進行批量更新,不妨接著往下看。
NSBatchUpdateRequest 批量更新
NSBatchUpdateRequest
是在 iOS 8, macOS 10.10 之后新添加的 API,它是專門用來進行批量更新的。因為用上面那種方式批量更新的話,會存在一個問題,就是更新前需要將要更新的數據,查詢出來,加載到內存中;這在數據量非常大的時候,假如說要更新十萬條數據,就比較麻煩了,因為對于手機這種內存比較小的設備,直接加載這么多數據到內存里顯然是不可能的。解決辦法就是每次只查詢出讀取一小部分數據到內存中,然后對其進行更新,更新完之后,再更新下一批,就這樣分批來處理。但這顯然不是高效的解決方案。
于是就有了 NSBatchUpdateRequest
這個 API。它的工作原理是不加載到內存里,而是直接對本地數據庫中數據進行更新。這就避免了內存不足的問題;但同時,由于是直接更新數據庫,所以內存中的 NSManagedObjectContext
不會知道數據庫的變化,解決辦法是調用 NSManagedObjectContext
的 + (void)mergeChangesFromRemoteContextSave:(NSDictionary*)changeNotificationData intoContexts:(NSArray<NSManagedObjectContext*> *)contexts;
方法來告訴 context,有哪些數據更新了。
下面來看一下 NSBatchUpdateRequest
的用法。
-
創建
// 根據 entity 創建 NSBatchUpdateRequest *updateRequest = [[NSBatchRequest alloc] initWithEntity:[Student entity]]; // 根據 entityName 創建 NSBatchUpdateRequest *updateRequest = [[NSBatchUpdateRequest alloc] initWithEntityName:@"Student"];
-
predicate
NSBatchUpdateRequest
的predicate
用來指定更新條件,例如這里指定更新 studentAge 等于 20 的學生updateRequest.predicate = [NSPredicate predicateWithFormat:@"studentAge == %@", @(20)];
-
propertiesToUpdate
propertiesToUpdate
屬性是一個字典,用它來指定需要更新的字段,字典里的 key 就是要更新的字段名,value 就是要設置的新值。因為 Objective-C 字典里只能存儲對象類型,所以如果字段基本數據類型的的話,需要轉換成NSNumber
對象。updateRequest.propertiesToUpdate = @{@"studentName" : @"anotherName"};
-
resultType
resultType
屬性是NSBatchUpdateRequestResultType
類型的枚舉,用來指定返回的數據類型。這個枚舉有三個成員:-
NSStatusOnlyResultType
— 返回 BOOL 結果,表示更新是否執行成功 -
NSUpdatedObjectIDsResultType
— 返回更新成功的對象的 ID,是 NSArray<NSManagedObjectID *> * 類型。 -
NSUpdatedObjectsCountResultType
— 返回更新成功數據的總數,是數字類型
一般我們將其指定為
NSUpdatedObjectIDsResultType
updateRequest.resultType = NSUpdatedObjectIDsResultType;
-
-
executeRequest
配置完
NSBatchUpdateRequest
對象后,就可以通過 context 的- (nullable __kindof NSPersistentStoreResult *)executeRequest:(NSPersistentStoreRequest*)request error:(NSError **)error;
方法來執行批量更新了:NSBatchUpdateResult *updateResult = [self.context executeRequest:updateRequest error:&error]; NSArray<NSManagedObjectID *> *updatedObjectIDs = updateResult.result;
executeRequest
方法返回的是NSBatchUpdateResult
對象,里面有一個id result
屬性,它的具體類型就是前面通過枚舉成員指定的類型。 -
mergeChanges
底層數據更新之后,現在要通知內存中的 context 了,調用 context 的
mergeChanges
方法,第一個參數是個字典,指定要合并的數據類型(是更新還是刪除、插入等);第二個參數就是 context 數組,指定要合并到哪些 context 中去。NSDictionary *updatedDict = @{NSUpdatedObjectsKey : updatedObjectIDs}; [NSManagedObjectContext mergeChangesFromRemoteContextSave:updatedDict intoContexts:@[self.context]];
到這里,批量更新的操作就講完了,下面來看 NSBatchDeleteRequest
批量刪除。
NSBatchDeleteRequest 批量刪除
NSBatchDeleteRequest
的用法和 NSBatchUpdateRequest
很相似,不同的是 NSBatchDeleteRequest
需要指定 fetchRequest
屬性來進行刪除;而且它是 iOS 9 才添加進來的,和 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 中的增刪改查就講完了,下篇文章將會介紹 CoreData Model 中的 relationships 實現多表關聯的用法。