认识CoreData-初识CoreData

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> http://www.jianshu.com/p/c0e12a897971 `这段时间公司一直比较忙,和组里小伙伴一起把公司项目按照之前逻辑重写了一下。由于项目比较大,还要兼顾之前项目的迭代和其他项目,目前为止只写完第一阶段。之前项目本地持久化方案主要用的是SQLite,这次重写项目打算换一种持久化方案,于是我们经过讨论选择了苹果的“亲儿子”CoreData。在使用CoreData的过程中,我也是一边学习一边实践。在学习的过程中,一些写的质量比较高的博客对我的帮助也很大,例如objc.io等博客,在这里就不一一列举出来了,非常感谢这些作者。``先不说项目中用不用得到,其实很多人都是不了解CoreData的,但是经过我的学习发现CoreData还是挺不错的。所以正如这系列文章的名字一样-认识CoreData,打算写这系列文章来认识一下CoreData。这系列博客将从简单到复杂的来讲一下CoreData,其中除了基础使用还会包括多线程、批量数据处理等内容,这些很多都是我公司项目开发过程中接触到的,我们也设想了一些极端的情况,解决方案都会体现在这系列博客中。` `本人接触CoreData时间并不长,只是专门花了一段时间学习CoreData。本系列文章偏重于通过图形化界面使用CoreData,不会全部采取纯代码进行CoreData的所有操作,而且那样操作起来也确实比较麻烦,反而就失去了CoreData的优势和本质。` 文章中如有疏漏或错误,还请各位及时提出,谢谢!???? 写在前面在CoreData中有一些常用的类,称呼可能各不相同。所以这里先约定一些关键字,以便理解后面的一些内容,这些约定很多都是出现在苹果的官方文档中的。NSPersistentStoreCoordinator(Persistent Store Coordinator),缩写为PSC。NSManagedObjectContext(Managed Object Context),缩写为MOC。NSManagedObjectModel(Managed Object Model),缩写为MOM。NSManagedObject及其子类,根据英文翻译和其作用,称之为托管对象。后缀名为.xcdatamodeld的文件,因为存储着所有实体的数据结构和表示,所以称之为模型文件。 什么是CoreData?简单介绍一下CoreData出现在iOS3中,是苹果推出的一个数据存储框架。CoreData提供了一种对象关系映射(ORM)的存储关系,类似于Java的hibernate框架。CoreData可以将OC对象存储到数据库中,也可以将数据库中的数据转化为OC对象,在这个过程中不需要手动编写任何SQL语句,这是系统帮我们完成。 CoreData最大的优势就是使用过程中不需要编写任何SQL语句,CoreData封装了数据库的操作过程,以及数据库中数据和OC对象的转换过程。所以在使用CoreData的过程中,很多操作就像是对数据库进行操作一样,也有过滤条件、排序等操作。 这就相当于CoreData完成了Model层的大量工作,例如Model层的表示和持久化,有效的减少了开发的工作量,使Model层的设计更加面向对象。 CoreData好用吗?之前听人说过,CoreData比较容易入手,但是很难学精。这也是很多人说CoreData不好用的原因之一,只是因为使用方式有问题,或者说并没有真正掌握CoreData。 如果从性能上来说,CoreData比SQLite确实略差一些。但是对于移动端来说,并不需要大型网站的高并发,所以这点性能差别几乎是没有影响的,所以这点可以忽略不计。在后面的文章中,将会给出CoreData的优点和缺点对比,以及详细的性能测评。 CoreData主要的几个类NSManagedObjectContext托管对象上下文,进行数据操作时大多都是和这个类打交道。 NSManagedObjectModel托管对象模型,一个托管对象模型关联一个模型文件(.xcdatamodeld),存储着数据库的数据结构。 NSPersistentStoreCoordinator持久化存储协调器,负责协调存储区和上下文之间的关系。 NSManagedObject托管对象类,所有CoreData中的托管对象都必须继承自当前类,根据实体创建托管对象类文件。 CoreData简单创建流程模型文件操作1.1 创建模型文件,后缀名为.xcdatamodeld。创建模型文件之后,可以在其内部进行添加实体等操作(用于表示数据库文件的数据结构)1.2 添加实体(表示数据库文件中的表结构),添加实体后需要通过实体,来创建托管对象类文件。1.3 添加属性并设置类型,可以在属性的右侧面板中设置默认值等选项。(每种数据类型设置选项是不同的)1.4 创建获取请求模板、设置配置模板等。1.5 根据指定实体,创建托管对象类文件(基于NSManagedObject的类文件) 实例化上下文对象2.1 创建托管对象上下文(NSManagedObjectContext)2.2 创建托管对象模型(NSManagedObjectModel)2.3 根据托管对象模型,创建持久化存储协调器(NSPersistentStoreCoordinator)2.4 关联并创建本地数据库文件,并返回持久化存储对象(NSPersistentStore)2.5 将持久化存储协调器赋值给托管对象上下文,完成基本创建。 CoreData结构CoreData的结构构成之前看到过几张介绍CoreData结构的图片,感觉其表示的结构比较清晰。可以通过这几张图片初步认识一下CoreData,在后面的文章中还会对这几个类进行详细解释。 上图中是初始化MOC所涉及到的一些类,由这些类实例化并最终构成可以使用的MOC。图中编号是实例化一个具备数据处理能力的MOC过程,这个过程和上面介绍过的实例化上下文对象相同。 在PSC创建并关联本地数据库,并设置为MOC的persistentStoreCoordinator属性后,MOC就具备对当前存储区所有托管对象操作的能力。但是需要注意的是,MOC对托管对象是懒加载的,在使用时才会被加载到MOC的缓存中。 MOM对象加载模型文件后,获取到模型文件中所有实体的构成结构。由于MOM中存储着模型文件的结构,PSC需要通过MOM对象实例化本地数据库。 所有属性都存在Entity中,以及有关联关系的属性和请求模板,这都会在后面的章节中讲到。 可以通过Entity创建继承自NSManagedObject类的文件,这个文件就是开发中使用的托管对象,具备模型对象的表示功能,CoreData的本地持久化都是通过这个类及其子类完成的。 持久化存储调度器在CoreData的整体结构中,主要分为两部分。一个是NSManagedObjectContext管理的模型部分,管理着所有CoreData的托管对象。一个是SQLite实现的本地持久化部分,负责和SQL数据库进行数据交互,主要由NSPersistentStore类操作。这就构成了CoreData的大体结构。 从图中可以看出,这两部分都是比较独立的,两部分的交互由一个持久化存储调度器(NSPersistentStoreCoordinator)来控制。上层NSManagedObjectContext存储的数据都是交给持久化调度器,由调度器调用具体的持久化存储对象(NSPersistentStore)来操作对应的数据库文件,NSPersistentStore负责存储的实现细节。这样就很好的将两部分实现了分离。 个人随想对于CoreData的整体结构,因为CoreData底层存储本来就是用SQLite实现的,所以我用CoreData的结构和SQLite对比了一下,发现还是很多相似之处的。 .xcdatamodeld文件代表着数据库文件结构,通过.xcdatamodeld编译后的.momd文件生成数据库。每个实体代表一张数据表,实体之间的关联关系就是SQLite的外键。 下图就是CoreData底层存储的结构,用红圈圈住的部分指向关联表的主键下标。例如1就指向关联表的主键下标为1的行。 CoreData杂谈CoreData数据存储安全CoreData本质还是使用SQLite进行存储,并没有另外提供加密功能,具体的数据加解密还需要自己完成。 CoreData在硬盘上的数据存储结构: 通过PSC指定创建SQLite目录后,会在指定的目录下生成一个数据库文件,同时还会生成两个同名但后缀不同的文件,其中只有后缀.sqlite的文件是存储数据的文件。 这个数据库文件中会默认生成三个表,Z_METADATA、Z_PRIMARYKEY、Z_MODELCACHE,其他我们自己的表也都是大写Z开头的。 在每个表中,系统还会默认生成三个字段,Z_PK、Z_ENT、Z_OPT三个字段,也都是大写Z开头并且带下划线的。其他字段就是我们自己的字段了,大写Z开头但不带下划线。 CoreData执行效率现在市面上的大多数项目,都是使用SQLite作为持久化的方案,而CoreData的使用并不是很普遍。对于这个问题,我认为首先是很多项目开始的比较早,那时候好多iOS程序员都是从其他语言转过来的,更加熟悉SQLite,所以用SQLite比较多一些。后面如果不进行大的项目重构,就很难换其他的持久化方案了。 还有就是不熟悉CoreData,也不想去了解和深入学习CoreData,我认为这是很大的原因。所以项目中用CoreData的人并不多,而真正掌握CoreData技术的人更少。 之前听其他人说CoreData的执行效率不如SQLite高,这个如果深究的话,确实CoreData要比SQLite效率差一些,只不过并没有太大区别。CoreData本质也是在底层执行SQL语句,只是CoreData的SQL语句执行逻辑比较耗时,没有手动编写SQL语句更加直接。我们可以将CoreData的调试功能打开,具体看一下SQL语句的执行。 这里要说一点,客户端毕竟不是服务端,不需要像服务器那样大量的数据查询,所以CoreData是完全可以应对客户端的查询量的。如果从灵活性来说,CoreData确实没有SQLite的灵活性高,一些SQLite的复杂功能可能也不能实现,但是就目前大多数项目来说,CoreData已经能够满足项目持久化需求了。 ...

June 24, 2019 · 1 min · jiezi

认识CoreData-基础使用

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> http://www.jianshu.com/p/0ddfa35c7898 第一篇文章中并没有讲CoreData的具体用法,只是对CoreData做了一个详细的介绍,算是一个开始和总结吧。这篇文章中会主要讲CoreData的基础使用,以及在使用中需要注意的一些细节。因为文章中会插入代码和图片,内容可能会比较多,比较考验各位耐心。 文章中如有疏漏或错误,还请各位及时提出,谢谢!????` 创建自带CoreData的工程在新建一个项目时,可以勾选Use Core Data选项,这样创建出来的工程系统会默认生成一些CoreData的代码以及一个.xcdatamodeld后缀的模型文件,模型文件默认以工程名开头。这些代码在AppDelegate类中,也就是代表可以在全局使用AppDelegate.h文件中声明的CoreData方法和属性。 系统默认生成的代码是非常简单的,只是生成了基础的托管对象模型、托管对象上下文、持久化存储调度器,以及MOC的save方法。但是这些代码已经可以完成基础的CoreData操作了。 这部分代码不应该放在AppDelegate中,尤其对于大型项目来说,更应该把这部分代码单独抽离出去,放在专门的类或模块来管理CoreData相关的逻辑。所以我一般不会通过这种方式创建CoreData,我一般都是新建一个“干净”的项目,然后自己往里面添加,这样对于CoreData的完整使用流程掌握的也比较牢固。 CoreData模型文件的创建构建模型文件使用CoreData的第一步是创建后缀为.xcdatamodeld的模型文件,使用快捷键Command + N,选择Core Data -> Data Model -> Next,完成模型文件的创建。 创建完成后可以看到模型文件左侧列表,有三个选项Entities、Fetch Requests、Configurations,分别对应着实体、请求模板、配置信息。 添加实体现在可以通过长按左侧列表下方的Add Entity按钮,会弹出Add Entity、Add Fetch Request、Add Configuration选项,可以添加实体、请求模板、配置信息。这里先选择Add Entity来添加一个实体,命名为Person。 添加Person实体后,会发现一个实体对应着三部分内容,Attributes、Relationships、Fetched Properties,分别对应着属性、关联关系、获取操作。 现在对Person实体添加两个属性,添加age属性并设置type为Integer 16,添加name属性并设置type为String。 实体属性类型在模型文件的实体中,参数类型和平时创建继承自NSObject的模型类大体类似,但是还是有一些关于类型的说明,下面简单的列举了一下。 Undefined: 默认值,参与编译会报错Integer 16: 整数,表示范围 -32768 ~ 32767Integer 32: 整数,表示范围 -2147483648 ~ 2147483647Integer 64: 整数,表示范围 –9223372036854775808 ~ 9223372036854775807Float: 小数,通过MAXFLOAT宏定义来看,最大值用科学计数法表示是 0x1.fffffep+127fDouble: 小数,小数位比Float更精确,表示范围更大String: 字符串,用NSString表示Boolean: 布尔值,用NSNumber表示Date: 时间,用NSDate表示Binary Data: 二进制,用NSData表示Transformable: OC对象,用id表示。可以在创建托管对象类文件后,手动改为对应的OC类名。使用的前提是,这个OC对象必须遵守并实现NSCoding协议添加实体关联关系创建两个实体Department和Employee,并且在这两个实体中分别添加一些属性,下面将会根据这两个实体来添加关联关系。 ...

June 24, 2019 · 3 min · jiezi

认识CoreData-使用进阶

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> 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"]过滤集合对象可以通过NSPredicate对iOS中的集合对象执行过滤操作,可以是NSArray、NSSet及其子类。 对不可变数组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参数,可以设置三种复合条件,枚举值非常直观很容易看懂。 NSNotPredicateTypeNSAndPredicateTypeNSOrPredicateType基础语法下面是列举的一些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*"]keyPathNSPredicate在创建查询条件时,还支持设置被匹配目标的keyPath,也就是设置更深层被匹配的目标。例如下面设置employee的name属性为查找条件,就是用点语法设置的keyPath。 [NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"]设置查询条件在之前的文章中,执行下面MOC的fetchRequest方法,一般都需要传入一个NSFetchRequest类型的参数。这个request参数可以做一些设置操作,这样就可以以较优的性能获取指定的数据。 ...

June 24, 2019 · 4 min · jiezi

认识CoreData-多线程

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> http://www.jianshu.com/p/283e67ba12a3 CoreData使用相关的技术点已经讲差不多了,我所掌握的也就这么多了....在本篇文章中主要讲CoreData的多线程,其中会包括并发队列类型、线程安全等技术点。我对多线程的理解可能不是太透彻,文章中出现的问题还请各位指出。在之后公司项目使用CoreData的过程中,我会将其中遇到的多线程相关的问题更新到文章中。 在文章的最后,会根据我对CoreData多线程的学习,以及在工作中的具体使用,给出一些关于多线程结构的设计建议,各位可以当做参考。 文章中如有疏漏或错误,还请各位及时提出,谢谢!???? MOC并发队列类型在CoreData中MOC是支持多线程的,可以在创建MOC对象时,指定其并发队列的类型。当指定队列类型后,系统会将操作都放在指定的队列中执行,如果指定的是私有队列,系统会创建一个新的队列。但这都是系统内部的行为,我们并不能获取这个队列,队列由系统所拥有,并由系统将任务派发到这个队列中执行的。 NSManagedObjectContext并发队列类型:NSConfinementConcurrencyType : 如果使用init方法初始化上下文,默认就是这个并发类型。这个枚举值是不支持多线程的,从名字上也体现出来了。NSPrivateQueueConcurrencyType : 私有并发队列类型,操作都是在子线程中完成的。NSMainQueueConcurrencyType : 主并发队列类型,如果涉及到UI相关的操作,应该考虑使用这个枚举值初始化上下文。其中NSConfinementConcurrencyType类型在iOS9之后已经被苹果废弃,不建议使用这个API。使用此类型创建的MOC,调用某些比较新的CoreData的API可能会导致崩溃。 MOC多线程调用方式在CoreData中MOC不是线程安全的,在多线程情况下使用MOC时,不能简单的将MOC从一个线程中传递到另一个线程中使用,这并不是CoreData的多线程,而且会出问题。对于MOC多线程的使用,苹果给出了自己的解决方案。 在创建的MOC中使用多线程,无论是私有队列还是主队列,都应该采用下面两种多线程的使用方式,而不是自己手动创建线程。调用下面方法后,系统内部会将任务派发到不同的队列中执行。可以在不同的线程中调用MOC的这两个方法,这个是允许的。 - (void)performBlock:(void (^)())block 异步执行的block,调用之后会立刻返回。- (void)performBlockAndWait:(void (^)())block 同步执行的block,调用之后会等待这个任务完成,才会继续向下执行。 下面是多线程调用的示例代码,在多线程的环境下执行MOC的save方法,就是将save方法放在MOC的block体中异步执行,其他方法的调用也是一样的。 [context performBlock:^{ [context save:nil];}];但是需要注意的是,这两个block方法不能在NSConfinementConcurrencyType类型的MOC下调用,这个类型的MOC是不支持多线程的,只支持其他两种并发方式的MOC。 多线程的使用在业务比较复杂的情况下,需要进行大量数据处理,并且还需要涉及到UI的操作。对于这种复杂需求,如果都放在主队列中,对性能和界面流畅度都会有很大的影响,导致用户体验非常差,降低屏幕FPS。对于这种情况,可以采取多个MOC配合的方式。 CoreData多线程的发展中,在iOS5经历了一次比较大的变化,之后可以更方便的使用多线程。从iOS5开始,支持设置MOC的parentContext属性,通过这个属性可以设置MOC的父MOC。下面会针对iOS5之前和之后,分别讲解CoreData的多线程使用。 尽管现在的开发中早就不兼容iOS5之前的系统了,但是作为了解这里还是要讲一下,而且这种同步方式在iOS5之后也是可以正常使用的,也有很多人还在使用这种同步方式,下面其他章节也是同理。 iOS5之前使用多个MOC在iOS5之前实现MOC的多线程,可以创建多个MOC,多个MOC使用同一个PSC,并让多个MOC实现数据同步。通过这种方式不用担心PSC在调用过程中的线程问题,MOC在使用PSC进行save操作时,会对PSC进行加锁,等当前加锁的MOC执行完操作之后,其他MOC才能继续执行操作。 每一个PSC都对应着一个持久化存储区,PSC知道存储区中数据存储的数据结构,而MOC需要使用这个PSC进行save操作的实现。 这样做有一个问题,当一个MOC发生改变并持久化到本地时,系统并不会将其他MOC缓存在内存中的NSManagedObject对象改变。所以这就需要我们在MOC发生改变时,将其他MOC数据更新。 根据上面的解释,在下面例子中创建了一个主队列的mainMOC,主要用于UI操作。一个私有队列的backgroundMOC,用于除UI之外的耗时操作,两个MOC使用的同一个PSC。 // 获取PSC实例对象- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { // 创建托管对象模型,并指明加载Company模型文件 NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"Company" withExtension:@"momd"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath]; // 创建PSC对象,并将托管对象模型当做参数传入,其他MOC都是用这一个PSC。 NSPersistentStoreCoordinator *PSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; // 根据指定的路径,创建并关联本地数据库 NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite", @"Company"]; [PSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil]; return PSC;}// 初始化用于本地存储的所有MOC- (void)createManagedObjectContext { // 创建PSC实例对象,其他MOC都用这一个PSC。 NSPersistentStoreCoordinator *PSC = self.persistentStoreCoordinator; // 创建主队列MOC,用于执行UI操作 NSManagedObjectContext *mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; mainMOC.persistentStoreCoordinator = PSC; // 创建私有队列MOC,用于执行其他耗时操作 NSManagedObjectContext *backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; backgroundMOC.persistentStoreCoordinator = PSC; // 通过监听NSManagedObjectContextDidSaveNotification通知,来获取所有MOC的改变消息 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];}// MOC改变后的通知回调- (void)contextChanged:(NSNotification *)noti { NSManagedObjectContext *MOC = noti.object; // 这里需要做判断操作,判断当前改变的MOC是否我们将要做同步的MOC,如果就是当前MOC自己做的改变,那就不需要再同步自己了。 // 由于项目中可能存在多个PSC,所以下面还需要判断PSC是否当前操作的PSC,如果不是当前PSC则不需要同步,不要去同步其他本地存储的数据。 [MOC performBlock:^{ // 直接调用系统提供的同步API,系统内部会完成同步的实现细节。 [MOC mergeChangesFromContextDidSaveNotification:noti]; }];}在上面的Demo中,创建了一个PSC,并将其他MOC都关联到这个PSC上,这样所有的MOC执行本地持久化相关的操作时,都是通过同一个PSC进行操作的。并在下面添加了一个通知,这个通知是监听所有MOC执行save操作后的通知,并在通知的回调方法中进行数据的合并。 ...

June 24, 2019 · 2 min · jiezi

认识CoreData-高级用法

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> http://www.jianshu.com/p/01f36026da7d 在之前的文章中,已经讲了很多关于CoreData使用相关的知识点。这篇文章中主要讲两个方面,NSFetchedResultsController和版本迁移。文章题目中虽然有“高级”两个字,其实讲的东西并不高级,只是因为上一篇文章中东西太多了,把两个较复杂的知识点挪到这篇文章中。???? 文章中如有疏漏或错误,还请各位及时提出,谢谢!???? NSFetchedResultsController在开发过程中会经常用到UITableView这样的视图类,这些视图类需要自己管理其数据源,包括网络获取、本地存储都需要写代码进行管理。 而在CoreData中提供了NSFetchedResultsController类(fetched results controller,也叫FRC),FRC可以管理UITableView或UICollectionView的数据源。这个数据源主要指本地持久化的数据,也可以用这个数据源配合着网络请求数据一起使用,主要看业务需求了。 本篇文章会使用UITableView作为视图类,配合NSFetchedResultsController进行后面的演示,UICollectionView配合NSFetchedResultsController的使用也是类似,这里就不都讲了。 简单介绍就像上面说到的,NSFetchedResultsController就像是上面两种视图的数据管理者一样。FRC可以监听一个MOC的改变,如果MOC执行了托管对象的增删改操作,就会对本地持久化数据发生改变,FRC就会回调对应的代理方法,回调方法的参数会包括执行操作的类型、操作的值、indexPath等参数。 实际使用时,通过FRC“绑定”一个MOC,将UITableView嵌入在FRC的执行流程中。在任何地方对这个“绑定”的MOC存储区做修改,都会触发FRC的回调方法,在FRC的回调方法中嵌入UITableView代码并做对应修改即可。 由此可以看出FRC最大优势就是,始终和本地持久化的数据保持统一。只要本地持久化的数据发生改变,就会触发FRC的回调方法,从而在回调方法中更新上层数据源和UI。这种方式讲的简单一点,就可以叫做数据带动UI。 但是需要注意一点,在FRC的初始化中传入了一个MOC参数,FRC只能监测传入的MOC发生的改变。假设其他MOC对同一个存储区发生了改变,FRC则不能监测到这个变化,不会做出任何反应。 所以使用FRC时,需要注意FRC只能对一个MOC的变化做出反应,所以在CoreData持久化层设计时,尽量一个存储区只对应一个MOC,或设置一个负责UI的MOC,这在后面多线程部分会详细讲解。 修改模型文件结构在写代码之前,先对之前的模型文件结构做一些修改。 讲FRC的时候,只需要用到Employee这一张表,其他表和设置直接忽略。需要在Employee原有字段的基础上,增加一个String类型的sectionName字段,这个字段就是用来存储section title的,在下面的文章中将会详细讲到。 初始化FRC下面例子是比较常用的FRC初始化方式,初始化时指定的MOC,还用之前讲过的MOC初始化代码,UITableView初始化代码这里也省略了,主要突出FRC的初始化。 // 创建请求对象,并指明操作Employee表NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];// 设置排序规则,指明根据height字段升序排序NSSortDescriptor *heightSort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];request.sortDescriptors = @[heightSort];// 创建NSFetchedResultsController控制器实例,并绑定MOCNSError *error = nil;fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:@"sectionName" cacheName:nil];// 设置代理,并遵守协议fetchedResultController.delegate = self;// 执行获取请求,执行后FRC会从持久化存储区加载数据,其他地方可以通过FRC获取数据[fetchedResultController performFetch:&error];// 错误处理if (error) { NSLog(@"NSFetchedResultsController init error : %@", error);}// 刷新UI[tableView reloadData];在上面初始化FRC时,传入的sectionNameKeyPath:参数,是指明当前托管对象的哪个属性当做section的title,在本文中就是Employee表的sectionName字段为section的title。从NSFetchedResultsSectionInfo协议的indexTitle属性获取这个值。 ...

June 24, 2019 · 3 min · jiezi

认识CoreData-MagicalRecord

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> http://www.jianshu.com/p/61b7a615508c 到目前为止,已经将CoreData相关的知识点都讲完了。在这篇文章中,主要讲一个CoreData第三方库-MagicalRecord。目前为止这个第三方在Github上有9500+的Star,是所有CoreData第三方库中使用最多、功能最全的。在文章的后面还会对CoreData做一个总结,以及对本系列所有文章做一个总结。 文章中如有疏漏或错误,还请各位及时提出,谢谢!???? MagicalRecordCoreData是苹果自家推出的一个持久化框架,使用起来更加面向对象。但是在使用过程中会出现大量代码,而且CoreData学习曲线比较陡峭,如果掌握不好,在使用过程中很容易造成其他问题。 国外开发者开源了一个基于CoreData封装的第三方——MagicalRecord,就像是FMDB封装SQLite一样,MagicalRecord封装的CoreData,使得原生的CoreData更加容易使用。并且MagicalRecord降低了CoreData的使用门槛,不用去手动管理之前的PSC、MOC等对象。 根据Github上MagicalRecord的官方文档,MagicalRecord的优点主要有三条: 1. 清理项目中CoreData代码2. 支持清晰、简单、一行式的查询操作3. 当需要优化请求时,可以获取NSFetchRequest进行修改 添加MagicalRecord到项目中将MagicalRecord添加到项目中,和使用其他第三方一样,可以通过下载源码和CocoaPods两种方式添加。 1. 从Github下载MagicalRecord源码,将源码直接拖到项目中,后续需要手动更新源码。 2. 也可以通过CocoaPods安装MagicalRecord,需要在Podfile中加入下面命令,后续只需要通过命令来更新。 pod "MagicalRecord"在之前创建新项目时,通过勾选"Use Core Data"的方式添加CoreData到项目中,会在AppDelegate文件中生成大量CoreData相关代码。如果是大型项目,被占用的位置是很重要的。而对于MagicalRecord来说,只需要两行代码即可。 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 初始化CoreData堆栈,也可以指定初始化某个CoreData堆栈 [MagicalRecord setupCoreDataStack]; return YES; } - (void)applicationWillTerminate:(UIApplication *)application { // 在应用退出时,应该调用cleanUp方法 [MagicalRecord cleanUp]; }MagicalRecord是支持CoreData的.xcdatamodeld文件的,使得CoreData这一优点可以继续使用。建立数据结构时还是像之前使用CoreData一样,通过.xcdatamodeld文件的方式建立。 支持iCloudCoreData是支持iCloud的,MagicalRecord对iCloud相关的操作也做了封装,只需要使用MagicalRecord+iCloud.h类中提供的方法,就可以进行iCloud相关的操作。 例如下面是MagicalRecord+iCloud.h中的一个方法,需要将相关参数传入即可。 + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID localStoreNamed:(NSString *)localStore;创建上下文MagicalRecord对上下文的管理和创建也比较全面,下面是MagicalRecord提供的部分创建和获取上下文的代码。因为是给NSManagedObjectContext添加的Category,可以直接用NSManagedObjectContext类调用,使用非常方便。 但是需要注意,虽然系统帮我们管理了上下文对象,对于耗时操作仍然要放在后台线程中处理,并且在主线程中进行UI操作。 + [NSManagedObjectContext MR_context] 设置默认的上下文为它的父级上下文,并发类型为NSPrivateQueueConcurrencyType+ [NSManagedObjectContext MR_newMainQueueContext] 创建一个新的上下文,并发类型为NSMainQueueConcurrencyType+ [NSManagedObjectContext MR_newPrivateQueueContext] 创建一个新的上下文,并发类型为NSPrivateQueueConcurrencyType+ [NSManagedObjectContext MR_contextWithParent:] 创建一个新的上下文,允许自定义父级上下文,并发类型为NSPrivateQueueConcurrencyType+ [NSManagedObjectContext MR_contextWithStoreCoordinator:] 创建一个新的上下文,并允许自定义持久化存储协调器,并发类型为NSPrivateQueueConcurrencyType+ [NSManagedObjectContext MR_defaultContext] 获取默认上下文对象,项目中最基础的上下文对象,并发类型是NSMainQueueConcurrencyType增删改查MagicalRecord对NSManagedObject添加了一个Category,将增删改查等操作放在这个Category中,使得这些操作可以直接被NSManagedObject类及其子类调用。 ...

June 24, 2019 · 1 min · jiezi