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 里通常处理错误的方式。

查询条目

从数据库中查询数据,会用到三个类:NSFetchRequestNSPredicateNSSortDescriptor,分别说一下这三个类的作用:

  • 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<NSManagedObjectID *> * 类型。
    • NSUpdatedObjectsCountResultType — 返回更新成功数据的总数,是数字类型

    一般我们将其指定为 NSUpdatedObjectIDsResultType

    updateRequest.resultType = NSUpdatedObjectIDsResultType;
    
  5. 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 属性,它的具体类型就是前面通过枚举成员指定的类型。

  6. 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 实现多表关联的用法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容