认识CoreData-使用进阶

作者: 刘小壮 | 来源:发表于2016-07-22 19:16 被阅读10743次
    该文章属于<简书 — 刘小壮>原创,转载请注明:

    <简书 — 刘小壮> http://www.jianshu.com/p/a4710356244d


    之前两篇文章都比较偏理论,文字表达比较多一些,但都是干货!学习时先理解理论知识,才能更好的帮助后面的理解。

    在这篇文章中,将会涉及关于CoreData的一些复杂操作,这些操作会涉及分页查询、模糊查询、批处理等高级操作。

    通过这些操作可以更好的使用CoreData,提升CoreData性能。文章中将会出现大量示例代码,通过代码的方式更有助于理解。
    文章内容还会比较多,希望各位耐心看完。

    文章中如有疏漏或错误,还请各位及时提出,谢谢!😊


    占位图

    NSPredicate

    概述

    iOS开发过程中,很多需求都需要用到过滤条件。例如过滤一个集合对象中存储的对象,可以通过Foundation框架下的NSPredicate类来执行这个操作。

    CoreData中可以通过设置NSFetchRequest类的predicate属性,来设置一个NSPredicate类型的谓词对象当做过滤条件。通过设置这个过滤条件,可以只获取符合过滤条件的托管对象,不会将所有托管对象都加载到内存中。这样是非常节省内存和加快查找速度的,设计一个好的NSPredicate可以优化CoreData搜索性能。

    语法

    NSPredicate更加偏向于自然语言,不像SQLite一样有很多固定的语法,看起来也更加清晰易懂。例如下面需要查找条件为年龄30岁以上,并且包括30岁的条件。

    [NSPredicate predicateWithFormat:@"age >= 30"]
    
    过滤集合对象

    可以通过NSPredicateiOS中的集合对象执行过滤操作,可以是NSArrayNSSet及其子类。

    对不可变数组NSArray执行的过滤,过滤后会返回一个NSArray类型的结果数组,其中存储着符合过滤条件的对象。

    NSArray *results = [array filteredArrayUsingPredicate:predicate]
    

    对可变数组NSMutableArray执行的过滤条件,过滤后会直接改变原集合对象内部存储的对象,删除不符合条件的对象。

    [arrayM filterUsingPredicate:predicate]
    
    复合过滤条件

    谓词不只可以过滤简单条件,还可以过滤复杂条件,设置复合过滤条件。

    [NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]
    

    当然也可以通过NSCompoundPredicate对象来设置复合过滤条件,返回结果是一个NSPredicate的子类NSCompoundPredicate对象。

    [[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:@[predicate1, predicate2]]
    

    枚举值NSCompoundPredicateType参数,可以设置三种复合条件,枚举值非常直观很容易看懂。

    • NSNotPredicateType
    • NSAndPredicateType
    • NSOrPredicateType
    基础语法

    下面是列举的一些NSPredicate的基础语法,这些语法看起来非常容易理解,更复杂的用法可以去看苹果的官方API

    语法 作用
    == 判断是否相等
    >= 大于或等于
    <= 小于或等于
    > 大于
    < 小于
    != 不等于
    AND 或 &&
    OR 或 II
    NOT 或 !
    正则表达式

    NSPredicate中还可以使用正则表达式,可以通过正则表达式完成一些复杂需求,这使得谓词的功能更加强大,例如下面是一个手机号验证的正则表达式

    NSString *mobile = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";
    NSPredicate *regexmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", mobile];
    
    模糊查询

    NSPredicate支持对数据的模糊查询,例如下面使用通配符来匹配包含lxz的结果,具体CoreData中的使用在下面会讲到。

    [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"]
    
    keyPath

    NSPredicate在创建查询条件时,还支持设置被匹配目标的keyPath,也就是设置更深层被匹配的目标。例如下面设置employeename属性为查找条件,就是用点语法设置的keyPath

    [NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"]
    

    设置查询条件

    在之前的文章中,执行下面MOCfetchRequest方法,一般都需要传入一个NSFetchRequest类型的参数。这个request参数可以做一些设置操作,这样就可以以较优的性能获取指定的数据。

    - (nullable NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error;
    

    NSFetchRequest

    在执行fetch操作前,可以给NSFetchRequest设置一些参数,这些参数包括谓词、排序等条件,下面是一些基础的设置。

    • 设置查找哪个实体,从数据库的角度来看就是查找哪张表,通过fetchRequestWithEntityName:或初始化方法来指定表名。

    • 通过NSPredicate类型的属性,可以设置查找条件,这个属性在开发中用得最多。NSPredicate可以包括固定格式的条件以及正则表达式

    • 通过sortDescriptors属性,可以设置获取结果数组的排序方式,这个属性是一个数组类型,也就是可以设置多种排序条件。(但是注意条件不要冲突)

    • 通过fetchOffset属性设置从查询结果的第几个开始获取,通过fetchLimit属性设置每次获取多少个。主要用于分页查询,后面会讲。

    MOC执行fetch操作后,获取的结果是以数组的形式存储的,数组中存储的就是托管对象。NSFetchRequest提供了参数resultType,参数类型是一个枚举类型。通过这个参数,可以设置执行fetch操作后返回的数据类型。

    • NSManagedObjectResultType: 返回值是NSManagedObject的子类,也就是托管对象,这是默认选项。

    • NSManagedObjectIDResultType: 返回NSManagedObjectID类型的对象,也就是NSManagedObjectID,对内存占用比较小。MOC可以通过NSManagedObjectID对象获取对应的托管对象,并且可以通过缓存NSManagedObjectID参数来节省内存消耗。

    • NSDictionaryResultType: 返回字典类型对象。

    • NSCountResultType: 返回请求结果的count值,这个操作是发生在数据库层级的,并不需要将数据加载到内存中。

    设置获取条件

    // 建立获取数据的请求对象,并指明操作Employee表
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 设置请求条件,通过设置的条件,来过滤出需要的数据
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
    request.predicate = predicate;
    
    // 设置请求结果排序方式,可以设置一个或一组排序方式,最后将所有的排序方式添加到排序数组中
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
    // NSSortDescriptor的操作都是在SQLite层级完成的,不会将对象加载到内存中,所以对内存的消耗是非常小的
    request.sortDescriptors = @[sort];
    
    // 执行获取请求操作,获取的托管对象将会被存储在一个数组中并返回
    NSError *error = nil;
    NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
    [employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Employee Name : %@, Height : %@, Brithday : %@", obj.name, obj.height, obj.brithday);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"CoreData Fetch Data Error : %@", error);
    }
    

    这里设置NSFetchRequest对象的一些请求条件,设置查找Employee表中namelxz的数据,并且将所有符合的数据用height升序的方式排列。

    有实体关联关系

    一个模型文件中的不同实体间,可以设置实体间的关联关系,这个在之前的文章中讲过。实体关联关系分为对一对多,也可以设置是否双向关联

    这里演示的实体只是简单的To One的关系,并且下面会给出设置是否双向关联的区别对比。

    插入实体

    // 创建托管对象,并将其关联到指定的MOC上
    Employee *zsEmployee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
    zsEmployee.name = @"zhangsan";
    zsEmployee.height = @1.9f;
    zsEmployee.brithday = [NSDate date];
    
    Employee *lsEmployee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
    lsEmployee.name = @"lisi";
    lsEmployee.height = @1.7f;
    lsEmployee.brithday = [NSDate date];
    
    Department *iosDepartment = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:context];
    iosDepartment.departName = @"iOS";
    iosDepartment.createDate = [NSDate date];
    iosDepartment.employee = zsEmployee;
    
    Department *androidDepartment = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:context];
    androidDepartment.departName = @"android";
    androidDepartment.createDate = [NSDate date];
    androidDepartment.employee = lsEmployee;
    
    // 执行存储操作
    NSError *error = nil;
    if (context.hasChanges) {
        [context save:&error];
    }
    
    // 错误处理
    if (error) {
        NSLog(@"Association Table Add Data Error : %@", error);
    }
    

    上面创建了四个实体,并且将Employee都关联到Department上,完成关联操作后通过MOC存储到本地。

    可以看到上面所有的托管对象创建时,都使用NSEntityDescriptioninsert方法创建,并和上下文建立关系。这时就想问了,我能直接采用传统的init方法创建吗?

    会崩的😱!创建托管对象时需要指定MOC,在运行时动态的生成setget方法。但是直接通过init方法初始化的对象,系统是不知道这里是需要系统自身生成setget方法的,而且系统也不知道应该对应哪个MOC,会导致方法未实现的崩溃。所以就出现了开发中经常出现的错误,如下面崩溃信息:

    -[Employee setName:]: unrecognized selector sent to instance 0x7fa665900f60
    

    双向关联

    在上一篇文章中提到过双向关联的概念,也就是设置RelationshipInverse是否为空。下面是EmployeeDepartment在数据库中,设置inverse和没有设置inverse的两种数据存储,可以很清晰的对比出设置双向关联的区别。
    测试代码还是用上面插入实体的代码,只是更改inverse选项。

    设置双向关联
    Employee Department
    未设置双向关联
    Employee Department

    从图中可以看出,未设置双向关联的实体,Department关联Employee为属性并存储后,Department表中的关系是存在的,但Employee表中的关系依然是空的。而设置双向关联后的实体,在Department关联Employee为属性并存储后,Employee在表中自动设置了和Department的关系。

    双向关联的关系不只体现在数据库中,在程序运行过程中托管对象的关联属性,也是随着发生变化的。双向关联的双方,一方的关联属性设置关系后,另一方关联属性的关系也会发生变化。用下面的代码打印一下各自的关联属性,结果和上面数据库的变化是一样的。

    NSLog(@"Department : %@, Employee : %@", androidDepartment.employee, lsEmployee.department);
    

    查询操作

    // 创建获取数据的请求对象,并指明操作Department表
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Department"];
    
    // 设置请求条件,设置employee的name为请求条件。NSPredicate的好处在于,可以设置keyPath条件
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"];
    request.predicate = predicate;
    
    // 执行查找操作
    NSError *error = nil;
    NSArray<Department *> *departments = [context executeFetchRequest:request error:&error];
    [departments enumerateObjectsUsingBlock:^(Department * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Department Search Result DepartName : %@, employee name : %@", obj.departName, obj.employee.name);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"Department Search Error : %@", error);
    }
    

    查找Department实体,并打印实体内容。就像上面讲的双向关系一样,有关联关系的实体,自己被查找出来后,也会将与之关联的其他实体也查找出来,并且查找出来的实体都是关联着MOC的。

    分页查询

    在从本地存储区获取数据时,可以指定从第几个获取,以及本次查询获取多少个数据,联合起来使用就是分页查询。当然也可以根据需求,单独使用这两个API

    这种需求在实际开发中非常常见,例如TableView中,上拉加载数据,每次加载20条数据,就可以利用分页查询轻松实现。

    // 创建获取数据的请求对象,并指明操作Employee表
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 设置查找起始点,这里是从搜索结果的第六个开始获取
    request.fetchOffset = 6;
    
    // 设置分页,每次请求获取六个托管对象
    request.fetchLimit = 6;
    
    // 设置排序规则,这里设置身高升序排序
    NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
    request.sortDescriptors = @[descriptor];
    
    // 执行查询操作
    NSError *error = nil;
    NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
    [employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Page Search Result Name : %@, height : %@", obj.name, obj.height);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"Page Search Data Error : %@", error);
    }
    

    上面是一个按照身高升序排序,分页获取搜索结果的例子。查找Employee表中的实体,将结果按照height字段升序排序,并从结果的第六个开始查找,并且设置获取的数量也是六个。

    模糊查询

    有时需要获取具有某些相同特征的数据,这样就需要对查询的结果做模糊匹配。在CoreData执行模糊匹配时,可以通过NSPredicate执行这个操作。

    // 创建获取数据的请求对象,设置对Employee表进行操作
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 创建模糊查询条件。这里设置的带通配符的查询,查询条件是结果包含lxz
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
    request.predicate = predicate;
    
    // 执行查询操作
    NSError *error = nil;
    NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
    [employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Fuzzy Search Result Name : %@, height : %@", obj.name, obj.height);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"Fuzzy Search Data Error : %@", error);
    }
    

    上面是使用通配符的方式进行模糊查询NSPredicate支持多种形式的模糊查询,下面列举一些简单的匹配方式。模糊查询条件对大小写不敏感,所以查询条件大小写均可。

    • 以lxz开头

      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", @"lxz"];
      
    • 以lxz结尾

      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name ENDSWITH %@", @"lxz"];
      
    • 其中包含lxz

      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains %@", @"lxz"];
      
    • 查询条件结果包含lxz

      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
      

    加载请求模板

    在之前的文章中谈到在模型文件中设置请求模板,也就是在.xcdatamodeld文件中,设置Fetch Requests,使用时可以通过对应的NSManagedObjectModel获取设置好的模板。

    .... 省略上下文创建步骤 ....
    // 通过MOC获取模型文件对应的托管对象模型
    NSManagedObjectModel *model = context.persistentStoreCoordinator.managedObjectModel;
    // 通过.xcdatamodeld文件中设置的模板名,获取请求对象
    NSFetchRequest *fetchRequest = [model fetchRequestTemplateForName:@"EmployeeFR"];
    
    // 请求数据,下面的操作和普通请求一样
    NSError *error = nil;
    NSArray<Employee *> *dataList = [context executeFetchRequest:fetchRequest error:&error];
    [dataList enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Employee.count = %ld, Employee.height = %f", dataList.count, [obj.height floatValue]);
    }];
    
    // 错误处理
    if (error) {
        NSLog(@"Execute Fetch Request Error : %@", error);
    }
    

    获取结果Count值

    开发过程中有时需要只获取所需数据的Count值,也就是执行获取操作后数组中所存储的对象数量。遇到这个需求,如果像之前一样MOC执行获取操作,获取到数组然后取Count,这样对内存消耗是很大的

    对于这个需求,苹果提供了两种常用的方式获取这个Count值。这两种获取操作,都是在数据库中完成的,并不需要将托管对象加载到内存中,对内存的开销也是很小的。

    方法1,设置resultType

    // 设置过滤条件,可以根据需求设置自己的过滤条件
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"];
    // 创建请求对象,并指明操作Employee表
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    fetchRequest.predicate = predicate;
    // 这一步是关键。设置返回结果类型为Count,返回结果为NSNumber类型
    fetchRequest.resultType = NSCountResultType;
    
    // 执行查询操作,返回的结果还是数组,数组中只存在一个对象,就是计算出的Count值
    NSError *error = nil;
    NSArray *dataList = [context executeFetchRequest:fetchRequest error:&error];
    NSInteger count = [dataList.firstObject integerValue];
    NSLog(@"fetch request result Employee.count = %ld", count);
    
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
    

    方法1中设置NSFetchRequest对象的resultTypeNSCountResultType,获取到结果的Count值。这个枚举值在之前的文章中提到过,除了Count参数,还可以设置其他三种参数。

    方法2,使用MOC提供的方法

    // 设置过滤条件
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"];
    // 创建请求对象,指明操作Employee表
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    fetchRequest.predicate = predicate;
    
    // 通过调用MOC的countForFetchRequest:error:方法,获取请求结果count值,返回结果直接是NSUInteger类型变量
    NSError *error = nil;
    NSUInteger count = [context countForFetchRequest:fetchRequest error:&error];
    NSLog(@"fetch request result count is : %ld", count);
    
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
    

    MOC提供了专门获取请求结果Count值的方法,通过这个方法可以直接返回一个NSUInteger类型的Count值,使用起来比上面的方法更方便点,其他都是一样的。

    位运算

    假设有需求是对Employee表中,所有托管对象的height属性计算总和。这个需求在数据量比较大的情况下,将所有托管对象加载到内存中是非常消耗内存的,就算批量加载也比较耗时耗内存。

    CoreData对于这样的需求,提供了位运算的功能。MOC在执行请求时,是支持对数据进行位运算的。这个操作依然是在数据库层完成的,对内存的占用非常小。

    // 创建请求对象,指明操作Employee表
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    // 设置返回值为字典类型,这是为了结果可以通过设置的name名取出,这一步是必须的
    fetchRequest.resultType = NSDictionaryResultType;
    
    // 创建描述对象
    NSExpressionDescription *expressionDes = [[NSExpressionDescription alloc] init];
    // 设置描述对象的name,最后结果需要用这个name当做key来取出结果
    expressionDes.name = @"sumOperatin";
    // 设置返回值类型,根据运算结果设置类型
    expressionDes.expressionResultType = NSFloatAttributeType;
    
    // 创建具体描述对象,用来描述对那个属性进行什么运算(可执行的运算类型很多,这里描述的是对height属性,做sum运算)
    NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"height"]]];
    // 只能对应一个具体描述对象
    expressionDes.expression = expression;
    // 给请求对象设置描述对象,这里是一个数组类型,也就是可以设置多个描述对象
    fetchRequest.propertiesToFetch = @[expressionDes];
    
    // 执行请求,返回值还是一个数组,数组中只有一个元素,就是存储计算结果的字典
    NSError *error = nil;
    NSArray *resultArr = [context executeFetchRequest:fetchRequest error:&error];
    // 通过上面设置的name值,当做请求结果的key取出计算结果
    NSNumber *number = resultArr.firstObject[@"sumOperatin"];
    NSLog(@"fetch request result is %f", [number floatValue]);
    
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
    
    执行结果
    执行结果

    从执行结果可以看到,MOC对所有查找到的托管对象height属性执行了求和操作,并将结果放在字典中返回。位运算主要是通过NSFetchRequest对象的propertiesToFetch属性设置,这个属性可以设置多个描述对象,最后通过不同的name当做key来取出结果即可。

    NSExpression类可以描述多种运算,可以在NSExpression.h文件中的注释部分,看到所有支持的运算类型,大概看了一下有二十多种运算。而且除了上面NSExpression调用的方法,此类还支持点语法的位运算,例如下面的例子。

    [NSExpression expressionWithFormat:@"@sum.height"];
    

    批处理

    在使用CoreData之前,我和公司同事也讨论过,假设遇到需要大量数据处理的时候怎么办。CoreData对于大量数据处理的灵活性肯定不如SQLite,这时候还需要自己使用其他方式优化数据处理。虽然在移动端这种情况很少出现,但是在持久层设计时还是要考虑这方面。

    当需要进行数据的处理时,CoreData需要先将数据加载到内存中,然后才能对数据进行处理。这样对于大量数据来说,都加载到内存中是非常消耗内存的,而且容易导致崩溃的发生。如果遇到更改所有数据的某个字段这样的简单需求,需要将相关的托管对象都加载到内存中,然后进行更改、保存。

    对于上面这样的问题,CoreDataiOS8推出了批量更新API,通过这个API可以直接在数据库一层就完成更新操作,而不需要将数据加载到内存。除了批量更新操作,在iOS9中还推出了批量删除API,也是在数据库一层完成的操作。关于批处理的API很多都是iOS8iOS9出来的,使用时需要注意版本兼容

    但是有个问题,批量更新和批量删除的两个API,都是直接对数据库进行操作,更新完之后会导致MOC缓存和本地持久化数据不同步的问题。所以需要手动刷新受影响的MOC中存储的托管对象,使MOC和本地统一。假设你使用了NSFetchedResultsController,为了保证界面和数据的统一,这一步更新操作更需要做。

    批量更新

    // 创建批量更新对象,并指明操作Employee表。
    NSBatchUpdateRequest *updateRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Employee"];
    // 设置返回值类型,默认是什么都不返回(NSStatusOnlyResultType),这里设置返回发生改变的对象Count值
    updateRequest.resultType = NSUpdatedObjectsCountResultType;
    // 设置发生改变字段的字典
    updateRequest.propertiesToUpdate = @{@"height" : [NSNumber numberWithFloat:5.f]};
    
    // 执行请求后,返回值是一个特定的result对象,通过result的属性获取返回的结果。MOC的这个API是从iOS8出来的,所以需要注意版本兼容。
    NSError *error = nil;
    NSBatchUpdateResult *result = [context executeRequest:updateRequest error:&error];
    NSLog(@"batch update count is %ld", [result.result integerValue]);
    
    // 错误处理
    if (error) {
        NSLog(@"batch update request result error : %@", error);
    }
    
    // 更新MOC中的托管对象,使MOC和本地持久化区数据同步
    [context refreshAllObjects];
    

    上面对Employee表中所有的托管对象height值做了批量更新,在更新时通过设置propertiesToUpdate字典来控制更新字段和更新的值,设置格式是字段名 : 新值。通过设置批处理对象的predicate属性,设置一个谓词对象来控制受影响的对象

    还可以对多个存储区(数据库)做同样批处理操作,通过设置其父类affectedStores属性,类型是一个数组,可以包含受影响的存储区,多个存储区的操作对批量删除同样适用

    MOC在执行请求方法时,发现方法名也不一样了,执行的是executeRequest: error:方法,这个方法是从iOS8之后出来的。方法传入的参数是NSBatchUpdateRequest类,此类并不是继承自NSFetchRequest类,而是直接继承自NSPersistentStoreRequest,和NSFetchRequest是平级关系。

    批量删除

    // 创建请求对象,并指明对Employee表做操作
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    // 通过谓词设置过滤条件,设置条件为height小于1.7
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < %f", 1.7f];
    fetchRequest.predicate = predicate;
    
    // 创建批量删除请求,并使用上面创建的请求对象当做参数进行初始化
    NSBatchDeleteRequest *deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest];
    // 设置请求结果类型,设置为受影响对象的Count
    deleteRequest.resultType = NSBatchDeleteResultTypeCount;
    
    // 使用NSBatchDeleteResult对象来接受返回结果,通过id类型的属性result获取结果
    NSError *error = nil;
    NSBatchDeleteResult *result = [context executeRequest:deleteRequest error:&error];
    NSLog(@"batch delete request result count is %ld", [result.result integerValue]);
    
    // 错误处理
    if (error) {
        NSLog(@"batch delete request error : %@", error);
    }
    
    // 更新MOC中的托管对象,使MOC和本地持久化区数据同步
    [context refreshAllObjects];
    

    大多数情况下,涉及到托管对象的操作,都需要将其加载到内存中完成。所以使用CoreData时,需要注意内存的使用,不要在内存中存在过多的托管对象。在已经做系统兼容的情况下,进行大量数据的操作时,应该尽量使用批处理来完成操作。

    需要注意的是,refreshAllObjects是从iOS9出来的,在iOS9之前因为要做版本兼容,所以需要使用refreshObject: mergeChanges:方法更新托管对象。

    异步请求

    // 创建请求对象,并指明操作Employee表
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 创建异步请求对象,并通过一个block进行回调,返回结果是一个NSAsynchronousFetchResult类型参数
    NSAsynchronousFetchRequest *asycFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult * _Nonnull result) {
        
        [result.finalResult enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"fetch request result Employee.count = %ld, Employee.name = %@", result.finalResult.count, obj.name);
        }];
    }];
    
    // 执行异步请求,和批量处理执行同一个请求方法
    NSError *error = nil;
    [context executeRequest:asycFetchRequest error:&error];
    
    // 错误处理
    if (error) {
        NSLog(@"fetch request result error : %@", error);
    }
    

    上面通过NSAsynchronousFetchRequest对象创建了一个异步请求,并通过block进行回调。如果有多个请求同时发起不需要担心线程安全的问题,系统会将所有的异步请求添加到一个操作队列中,在前一个任务访问数据库时,CoreData会将数据库加锁,等前面的执行完成才会继续执行后面的操作。

    NSAsynchronousFetchRequest提供了cancel方法,也就是可以在请求过程中,将这个请求取消。还可以通过一个NSProgress类型的属性,获取请求完成进度。NSAsynchronousFetchRequest类从iOS8开始可以使用,所以低版本需要做版本兼容。

    需要注意的是,执行请求时MOC并发类型不能是NSConfinementConcurrencyType,这个并发类型已经被抛弃,会导致崩溃。


    好多同学都问我有Demo没有,其实文章中贴出的代码组合起来就是个Demo。后来想了想,还是给本系列文章配了一个简单的Demo,方便大家运行调试,后续会给所有博客的文章都加上Demo

    Demo只是来辅助读者更好的理解文章中的内容,应该博客结合Demo一起学习,只看Demo还是不能理解更深层的原理Demo中几乎每一行代码都会有注释,各位可以打断点跟着Demo执行流程走一遍,看看各个阶段变量的值。

    Demo地址刘小壮的Github


    这两天更新了一下文章,将CoreData系列的六篇文章整合在一起,做了一个PDF版的《CoreData Book》,放在我Github上了。PDF上有文章目录,方便阅读。

    如果你觉得不错,请把PDF帮忙转到其他群里,或者你的朋友,让更多的人了解CoreData,衷心感谢!😁

    相关文章

      网友评论

      • 不进则退:请问一下:
        - 其中包含lxz
        `NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains %@", @"lxz"];`

        - 查询条件结果包含lxz
        `NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];`
        上面两个有什么区别?不是都是模糊查询吗?
        刘小壮:第二种是一种结构化的查询,表示被查询字符串在在结构中什么位置。在本例中,最后一种有前面三种的所有功能。
        刘小壮:但是第二种功能更多,主要第二种lxz前面和后面的星号,这表示是否以lxz开头或结尾,或者是在中间。
        刘小壮:从本例中这两种查询方式没有区别。
      • 8fe8946fa366:很喜欢博主写的文章,还给前一个文章打赏了两块钱,😁。
        刘小壮:谢谢~ 相互学习,共同进步。
      • 23cb714f1c82:请问lz是否知道如何使用coradata导出一张表?如何导入一张表?
        刘小壮:SQLite表吗
      • 小虾啦:讲的真的很仔细,我看了下refreshAllObjects 写的是iOS 8.3+ 这个有点疑问哦:smile:
        刘小壮:是的,iOS8.3出来的。文中写的不对。
      • 939aa01ddf09:我使用复合过滤条件使用[NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]这个无法正确筛选出来firstName对应的名称,改成[NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = \"XiaoZhuang\")"]这种才可以。博主,是否也是同样的情况呢?
        939aa01ddf09:应该不是的,我看了谓词的介绍,取字符需要添加'XiaoZhuang'或者"XiaoZhuang"才可以取值,你的Demo我还没有运行呢,我是自己敲了一个,然后遇到了这个问题。:smile:
        刘小壮:我感觉是不是存的问题,导致[NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]的方式取不出来。
        刘小壮:我当时是可以的,你可以下载我的Demo试试。
      • Sznag: reason: 'sum is not a supported method.' 使用位运算报错这个是因为什么
        Sznag:@刘小壮 嗯嗯 谢谢你👍
        刘小壮:根据错误提示,可能是你做位运算的属性,不支持位运算吧。例如文中的运算属性height,是浮点型的。
      • ifelseboyxx:博主,请教个问题啊:

        Xcode 8 关联 生成的 是个 NSet 数组,也就是:
        ```objc
        @property (nullable, nonatomic, retain) NSSet<Employee *> *employee;
        ```

        那像这中条件语言怎么写呢?:

        ```objc
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"];
        ```
        ifelseboyxx:@刘小壮 集合,说错了
        刘小壮:我第二篇《认识CoreData-基础使用》里,生成的也是NSSet<Employee *> *employee类型吧。
        刘小壮:问得我有点懵😳,NSSet不是数组吧。。。
      • Pusswzy:刘培壮?
        刘小壮:刘小壮
      • Bugfix:赞!,正在继续往后面的文章读~
        刘小壮:@比较纠结了 :blush:
        e54b69cb7854:赞! 写的好详细啊,coreData感觉好深奥,才了解皮毛。继续阅读大神博客学习
      • crosstrack:你好,请问什么工具能查看coredata中的数据啊
        刘小壮:@crosstrack CoreData本质上是基于SQLite实现的,可以打开数据库文件查看。
        crosstrack:不是很明白。。是打开哪个文件呢
        刘小壮:跟打开数据库一样
      • Aliv丶Zz:博主,如果我的一张表里有一个NSSet<student *>类型的字段 students ,如果对students进行增加 删除操作该怎么做!!!!
        刘小壮:@Aliv丶Zz 可以看看我Github上发的Demo,代码写的还比较全,文章里贴了地址。
        刘小壮:@Aliv丶Zz 调Context的save方法了吗?
        Aliv丶Zz: @Aliv丶Zz 如果直接用coredata 生成的方法,新增是OK的,删除操作重启APP后,删除数据依然存在
      • d053345b0b60:博主文章很正啊,说的很详细,大谢
        刘小壮:@Smile_521 :blush:
      • 南方小金豆:你coredata模型出来后,需要重新创建Employee 文件来使用的嘛?
        刘小壮:@那份牵挂给了谁 是的,创建后缀为.xcdatamodeld模型文件后,需要创建例如Employee的实体,然后根据实体生成对应的类文件,在编码中经常称其为模型类。在更新Employee实体后,需要将模型类也更新。
      • 来者可追_文过饰非:需要注意的是,refreshAllObjects是从iOS9出来的,在iOS9之前因为要做版本兼容,所以需要使用refreshObject: mergeChanges:方法更新托管对象。大神请教一下,这个方法的两个参数要怎么传呀
        刘小壮:@来者可追_文过饰非 每个NSManagedObject对象都有一个fault值,没有被缓存到内存中的对象fault为NO,这样比较节省内存开销,缓存之后fault为YES。refreshObject: mergeChanges:方法第一个参数传你需要更新的NSManagedObject对象,第二个参数传是否将其恢复为fault状态。
      • sincere_bs:写的很用心,赞
        刘小壮:@neilbee :smile:
      • 炳良哥哥:我用的magicrecord 封装起来的 也能这上面的需求吧
        刘小壮:@紫色的枫 可以

      本文标题:认识CoreData-使用进阶

      本文链接:https://www.haomeiwen.com/subject/rbeijttx.html