1. 程式人生 > >CoreData 從入門到精通 二 資料的增刪改查

CoreData 從入門到精通 二 資料的增刪改查

在上篇部落格中,講了資料模型和 CoreData 棧的建立,那下一步就是對資料的操作了。和資料庫一樣,CoreData 裡的操作也無非是增刪改查。下面我們將逐步講解在 CoreData 中進行增刪改查的方式。

基本的增刪改查

插入條目

先來看一下插入條目的方式,在插入之前,我們需要先建立要插入的資料, 使用 NSEntityDesctiption 類的 + (__kindof NSManagedObject *)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;

方法來建立一個新的 NSManagedObject 物件,入參分別是 entityNamemanagedObjectContextentityName 也就是實體類的名字,例如,我要插入一條新的 Student 欄位,entityName 就是 @”Student”;

contextNSManagedObjectContext物件,新增的實體類物件會新增到對應的 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 裡通常處理錯誤的方式。

查詢條目

從資料庫中查詢資料,會用到三個類:NSFetchRequestNSPredicate

NSSortDescriptor,分別說一下這三個類的作用:

  • NSFetchRequest — fetchRequest 代表了一條查詢請求,相當於 SQL 中的 SELECT 語句
  • NSPredicate — predicate 翻譯過來是謂詞的意思,它可以指定一些查詢條件,相當於 SQL 中的 WHERE 子句,有關 NSPredicate 的用法,可以看我之前寫過的一篇文章:使用 NSPredicate 進行資料庫查詢
  • NSSortDescriptor — sortDescriptor 是用來指定排序規則的,相當於 SQL 中的 ORDER BY 子句

NSFetchRequest 中有兩個屬性 predicatesortDescriptors,就是用來指定查詢的限制條件的。其中 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,就會出現相應的提示:

./fetchrequest-w528

創建出來的程式碼是這樣子的:

./fetchrequest2-w685

基本上常用到的程式碼都在這裡了。當然我們也可以自己來手寫出來,下面就是一個最簡單的查詢語句:

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 的用法。

  1. 建立

    // 根據 entity 建立
    NSBatchUpdateRequest *updateRequest = [[NSBatchRequest alloc] initWithEntity:[Student entity]];
    // 根據 entityName 建立
    NSBatchUpdateRequest *updateRequest = [[NSBatchUpdateRequest alloc] initWithEntityName:@"Student"];
  2. predicate

    NSBatchUpdateRequestpredicate用來指定更新條件,例如這裡指定更新 studentAge 等於 20 的學生

    updateRequest.predicate = [NSPredicate predicateWithFormat:@"studentAge == %@", @(20)];
  3. propertiesToUpdate

    propertiesToUpdate 屬性是一個字典,用它來指定需要更新的欄位,字典裡的 key 就是要更新的欄位名,value 就是要設定的新值。因為 Objective-C 字典裡只能儲存物件型別,所以如果欄位基本資料型別的的話,需要轉換成 NSNumber 物件。

    updateRequest.propertiesToUpdate = @{@"studentName" : @"anotherName"};
  4. resultType

    resultType 屬性是 NSBatchUpdateRequestResultType 型別的列舉,用來指定返回的資料型別。這個列舉有三個成員:

    • NSStatusOnlyResultType — 返回 BOOL 結果,表示更新是否執行成功
    • NSUpdatedObjectIDsResultType — 返回更新成功的物件的 ID,是 NSArray\

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 實現多表關聯的用法。