CoreData详解git
介绍:程序员
在Cocoa环境下,若是你想使用数据库(如sqlite),你可使用sql语句的方式经过相关的工具类进行数据库的直接操做。固然你也能够经过别人封装以后的一些简单框架,使得你的操做更加简单(如FMDB BNRPersistence)。github
Cocoa框架自己提供了CoreData这个API可方便的让开发者经过操做对象的方式在操做数据库。CoreData是一个对象图(object graph)以及持久化的管理框架。咱们能够经过CoreData创对象,设置好象之间的关系,而后将其持久化(咱们甚至可使用内存数据库),或者从硬盘上将持久化后的数据加载到内存中。对象图,咱们能够建立一个个的对象,并维持不一样对象之间的关系,一对一,一对多等。web
CoreData有大量的特性,诸如支持Redo,Undo的功能,这些不少Document based的程序中显得很是的有用。提供数据model结构变化轻量级的迁移方案。CoreData还经过Binding特性和控件的紧密结合,这样使得只须要少许的代码即可以完成强大的功能,下面是一个例子正则表达式
http://www.timisted.net/blog/archive/multiple-windows-with-core-data/sql
Core Data能够将数据存储为XML,二进制文件或SQLite文件。在Mac OS X 10.5 Leopard及之后的版本中,开发者也能够经过继承NSPersistentStore类以建立自定义的存储格式。每种方法都有其优缺点,例如XML的可读性,SQLite的节约空间等。数据库
Core Data的这一方面相似于原始的Enterprise Objects Framework(EOF)系统,但EOF中开发者可使用相对简洁的查询方式,而在Core Data中,只能使用一个语法相似SQL子集的查询语言,称为Predicate。Core Data是标准化的,能够自由的读写Xcode数据模型文件(一般是.xcdatamodel文件)。编程
与EOF不一样,Core Data目前没有设计多用户或多线程访问模式。模型迁移一般也须要代码,若其它开发者依赖于某个数据模型,则该数据模型的设计者可能在模型发生改变时须要与新数据模型一块儿提供版本转换代码。windows
Core Data由相对庞大的类继承体系组成,但开发者须要关注的接口只是其中的一个相对小的子集。数组
通常须要定义如下Core Data的三个必备
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
以及使用时须要用到的
NSFetchedResultsController *fetchedResultsController;
使用步骤:
还记得咱们每次使用CoreData的时候系统都会给咱们建立一些代码吗?
1 #pragma mark - Core Data 堆栈 2 //返回 被管理的对象上下文 3 - (NSManagedObjectContext *)managedObjectContext 4 { 5 if (_managedObjectContext) { 6 return _managedObjectContext; 7 } 8 9 NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 10 if (coordinator) { 11 _managedObjectContext = [[NSManagedObjectContext alloc] init]; 12 [_managedObjectContext setPersistentStoreCoordinator:coordinator]; 13 } 14 return _managedObjectContext; 15 } 16 17 // 返回 持久化存储协调者 18 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 19 { 20 if (_persistentStoreCoordinator) { 21 return _persistentStoreCoordinator; 22 } 23 24 NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataNotes.sqlite"]; 25 26 _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 27 28 [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 29 configuration:nil 30 URL:storeURL 31 options:nil 32 error:nil]; 33 34 return _persistentStoreCoordinator; 35 } 36 37 // 返回 被管理的对象模型 38 - (NSManagedObjectModel *)managedObjectModel 39 { 40 if (_managedObjectModel) { 41 return _managedObjectModel; 42 } 43 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataNotes" withExtension:@"momd"]; 44 _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 45 return _managedObjectModel; 46 } 47 48 #pragma mark - 应用程序沙箱 49 // 返回应用程序Docment目录的NSURL类型 50 - (NSURL *)applicationDocumentsDirectory 51 { 52 return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 53 }
下面是使用Core的示例代码
1 static NoteDAO *sharedManager = nil; 2 3 + (NoteDAO*)sharedManager 4 { 5 static dispatch_once_t once; 6 dispatch_once(&once, ^{ 7 8 sharedManager = [[self alloc] init]; 9 [sharedManager managedObjectContext]; 10 11 }); 12 return sharedManager; 13 } 14 15 16 //插入Note方法 17 -(int) create:(Note*)model 18 { 19 20 NSManagedObjectContext *cxt = [self managedObjectContext]; 21 22 NoteManagedObject *note = [NSEntityDescription insertNewObjectForEntityForName:@"Note" inManagedObjectContext:cxt]; 23 [note setValue: model.content forKey:@"content"]; 24 [note setValue: model.date forKey:@"date"]; 25 26 note.date = model.date; 27 note.content = model.content; 28 29 NSError *savingError = nil; 30 if ([self.managedObjectContext save:&savingError]){ 31 NSLog(@"插入数据成功"); 32 } else { 33 NSLog(@"插入数据失败"); 34 return -1; 35 } 36 37 return 0; 38 } 39 40 //删除Note方法 41 -(int) remove:(Note*)model 42 { 43 44 NSManagedObjectContext *cxt = [self managedObjectContext]; 45 46 NSEntityDescription *entityDescription = [NSEntityDescription 47 entityForName:@"Note" inManagedObjectContext:cxt]; 48 49 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 50 [request setEntity:entityDescription]; 51 52 NSPredicate *predicate = [NSPredicate predicateWithFormat: 53 @"date = %@", model.date]; 54 [request setPredicate:predicate]; 55 56 NSError *error = nil; 57 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 58 if ([listData count] > 0) { 59 NoteManagedObject *note = [listData lastObject]; 60 [self.managedObjectContext deleteObject:note]; 61 62 NSError *savingError = nil; 63 if ([self.managedObjectContext save:&savingError]){ 64 NSLog(@"删除数据成功"); 65 } else { 66 NSLog(@"删除数据失败"); 67 return -1; 68 } 69 } 70 71 return 0; 72 } 73 74 //修改Note方法 75 -(int) modify:(Note*)model 76 { 77 NSManagedObjectContext *cxt = [self managedObjectContext]; 78 79 NSEntityDescription *entityDescription = [NSEntityDescription 80 entityForName:@"Note" inManagedObjectContext:cxt]; 81 82 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 83 [request setEntity:entityDescription]; 84 85 NSPredicate *predicate = [NSPredicate predicateWithFormat: 86 @"date = %@", model.date]; 87 [request setPredicate:predicate]; 88 89 NSError *error = nil; 90 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 91 if ([listData count] > 0) { 92 NoteManagedObject *note = [listData lastObject]; 93 note.content = model.content; 94 95 NSError *savingError = nil; 96 if ([self.managedObjectContext save:&savingError]){ 97 NSLog(@"修改数据成功"); 98 } else { 99 NSLog(@"修改数据失败"); 100 return -1; 101 } 102 } 103 return 0; 104 } 105 106 //查询全部数据方法 107 -(NSMutableArray*) findAll 108 { 109 NSManagedObjectContext *cxt = [self managedObjectContext]; 110 111 NSEntityDescription *entityDescription = [NSEntityDescription 112 entityForName:@"Note" inManagedObjectContext:cxt]; 113 114 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 115 [request setEntity:entityDescription]; 116 117 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES]; 118 [request setSortDescriptors:@[sortDescriptor]]; 119 120 NSError *error = nil; 121 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 122 123 NSMutableArray *resListData = [[NSMutableArray alloc] init]; 124 125 for (NoteManagedObject *mo in listData) { 126 Note *note = [[Note alloc] init]; 127 note.date = mo.date; 128 note.content = mo.content; 129 [resListData addObject:note]; 130 } 131 132 return resListData; 133 } 134 135 //按照主键查询数据方法 136 -(Note*) findById:(Note*)model 137 { 138 NSManagedObjectContext *cxt = [self managedObjectContext]; 139 140 NSEntityDescription *entityDescription = [NSEntityDescription 141 entityForName:@"Note" inManagedObjectContext:cxt]; 142 143 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 144 [request setEntity:entityDescription]; 145 146 NSPredicate *predicate = [NSPredicate predicateWithFormat: 147 @"date = %@",model.date]; 148 [request setPredicate:predicate]; 149 150 NSError *error = nil; 151 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 152 153 if ([listData count] > 0) { 154 NoteManagedObject *mo = [listData lastObject]; 155 156 Note *note = [[Note alloc] init]; 157 note.date = mo.date; 158 note.content = mo.content; 159 160 return note; 161 } 162 return nil; 163 }
CoreData高级常识
关于CoreData貌似实际开发中不多用到,基本上是个有九个公司不会使用它,由于都说是性能很差,可是做为一个程序员,了解及其使用时必须了,
下面是我从一位大神那里搬过来的一下Core详细介绍,相信之后总有一天会帮我解决很多学习CoreData中的问题!
1、技术概览
1. Core Data 功能初窥
对于处理诸如对象生命周期管理、对象图管理等平常任务,Core Data框架提供了普遍且自动化的解决方案。它有如下特性。
(注:对象图-Object graph的解释:在面向对象编程中,对象之间有各类关系,例如对象直接引用另外的对象,或是经过引用链间接的引用其余对象,这些关系组成了网状的结构。 咱们把这些对象(和它们之间的联系)成为对象图。 对象图可大可小,有繁有简。 只包含单个字符串对象的数组就是一个简单的表明;而包含了application对象,引用windows, menus和相关视图对象、其余对象这样的结构就是复杂对象图的例子——这是在说mainwindow.xib。
有时,你可能想要把这样的对象图转化形式,让它们能够被保存到文件中,以使其余的进程或其余的机器能够再次将保存的内容读出,重购对象。 这样的过程常被成之为“归档”(Archiving)。
有些对象图是不完整的——一般称之为局部对象图(partial object graphs)。局部对象图包含了“占位符”(Placeholder)对象,所谓”占位符“,就是一些暂时无内容的对象,它们将再后期被具体化。一个典 型的例子就是nib文件中包含的File's Owner对象。
1) 对于key-value coding 和key-value observing完整且自动化的支持
除了为属性整合KVC和KVO的访问方法外, Core Data还整合了适当的集合访问方法来处理多值关系。
2) 自动验证属性(property)值
Core Data中的managed object扩展了标准的KVC 验证方法,以保证单个的数值在可接受的范围以内,从而使组合的值有意义。(需校准翻译)
3) 支持跟踪修改和撤销操做
对于撤销和重作的功能,除过用基本的文本编辑外,Core Data还提供内置的管理方式。
4) 关系的维护
Core Data管理数据的变化传播,包括维护对象间关系的一致性。
5) 在内存中和界面上分组、过滤、组织数据
6) 自动支持对象存储在外部数据仓库的功能
7) 建立复杂请求
你不须要动手去写复杂的SQL语句,就能够建立复杂的数据请求。方法是在“获取请求”(fetch request)中关联NSPredicate(又看到这个东东了,以前用它作过正则)。NSPrdicate支持基本的功能、相关子查询和其余高级的 SQL特性。它还支持正确的Unicode编码(不太懂,请高人指点), 区域感知查询(听说就是根据区域、语言设置调整查询的行为)、排序和正则表达式。
8) 延迟操做(原文为Futures(faulting)直译为期货,这里我的感受就是延迟操做的形象说法。请高人指教)。
Core Data 使用延迟加载(lazy loading)的方式减小内存负载。 它还支持部分实体化延迟加载,和“写时拷贝”的数据共享机制。(写时拷贝,说的是在复制对象的时候,实际上不生成新的空间,而是让对象共享一块存储区域, 在其内容发生改变的时候再分配)。
9) 合并的策略
Core Data 内置了版本跟踪和乐观锁定(optimistic locking)来支持多用户写入冲突的解决。
注:乐观锁,假定数据通常不出现冲突,因此在数据提交更新的时候,才对数据的冲突进行检测,若是冲突了,就返回冲突信息。
10) 数据迁移
就开发工做和运行时资源来讲,处理数据库架构的改变老是很复杂。Core Data的schema migration工具能够简化应对数据库结构变化的任务, 并且在某些状况下,容许你执行高效率的数据库原地迁移工做。
11) 可选择针对程序Controller层的集成,来支持UI的显示同步
Core Data在iPhone OS之上 提供NSFetchedResultsController对象来作相关工做,在Mac OS X上,咱们用Cocoa提供的绑定(Binding)机制来完成。
2. 为什么要使用Core Data
使用Core Data有不少缘由,其中最简单的一条就是:它能让你为Model层写的代码的行数减小为原来的50%到70%。 这归功于以前提到的Core Data的特性。更妙的是,对于上述特性你也既不用去测试,也不用花功夫去优化。
Core Data拥有成熟的代码,这些代码经过单元测试来保证品质。应用Core Data的程序天天被世界上几百万用户使用。经过了几个版本的发布,已经被高度优化。 它能利用Model层的信息和运行时的特性,而不经过程序层的代码实现。 除了提供强大的安全支持和错误处理外,它还提供了最优的内存扩展性,可实现有竞争力的解决方案。不使用Core Data的话,你须要花很长时间来起草本身的方案,解决各类问题,这样作效率不高。
除了Core Data自己的优势以外,使用它还有其余的好处: 它很容易和Mac OS X系统的Tool chain集成;利用Model设计工具能够按图形化方式轻松建立数据库的结构;你能够用Instruments的相关模板来测试Core Data的效率并debug。 在Mac OS X的桌面程序中,Core Data还和Interface Builder集成(打开Inspector能够看到有binding的选项,这个东东iPhone上木有。。。),按照model来建立UI变的更简单 了。 这些功能能更进一步的帮助你缩短设计、开发、测试程序的周期。
3. Core Data不是。。。
看了前面的介绍以后,咱们还须要了解一下关于Core Data常见的误解:
1) Core Data不是一个关系型数据库,也不是关系型数据库管理系统(RDBMS)。
Core Data 为数据变动管理、对象存储、对象读取恢复的功能提供了支持。 它可使用SQLite做为持久化存储的类型。 它自己并非一个数据库(这点很重要,好比,你可使用Core Data来记录数据变动,管理数据,但并不能用它向文件内存储数据)。
2) Core Data不是银弹
它并不能取代你写代码的工做。虽然能够纯粹使用XCode的数据建模工具和Interface Builder来编写复杂程序,但在更多的程序中,你都本身动手写代码。
3) Core Data并不依赖于Cocoa Bindings
Core Data + Cocoa Binding = 减小代码数量。但Core Data彻底能够在没有bindings的条件下使用。例如,能够编写一个没有UI,但包含Core Data的程序。
2、Core Data基础
1. Core Data基本架构
在大部分程序中,你要能经过某种方式打开一个包含对象归档的文件, 这个文件内至少要有一个根对象的引用。另外,还得能将全部的对象归档到文件中,若是你想要实现撤销的功能,就还要记录对象的更改状况。例如,在 Employee的示例程序中,你要能打开一个包含有employee和department对象归档的文件,并且这个文件至少包含了一个根对象——这 里,是一个包含全部employee的数组——请参考例图Figure 1。 相应的,你还要能将程序中的employee、department对象归档到文件中去。
Figure 1 按照Core Data文档结构管理的对象示意图
使用Core Data的框架,大多数的功能均可以自动实现,由于咱们有managed object context(管理对象的上下文,有时直接叫"Context")。managed object context就像是一个关卡,经过它能够访问框架底层的对象——这些对象的集合咱们称之为"persistence stack"(数据持久栈)。 managed object context做为程序中对象和外部的数据存储的中转站。栈的底部是persistence object stores(持久化数据存储),请看Figure 2的示意图。
Figure 2 使用Core Data的文档管理示意图
Core Data的使用并不限制在基于文档的程序中(document-based application)。你也能建立一个包含Core Data 的Utility程序(请查看Core Data Utility tutorial文档)。固然其余类型的程序也均可以使用Core Data。
被管理对象和上下文(Managed Objects and Contexts)
你能够把被管理对象上下文想象成一个”聪明“的便笺簿。当你从数据持久层获取对象时,就把这些临时的数据拷贝拿到写在本身的便笺簿上(固然,在便笺上对象 会 “恢复”之前的对象图结构)。而后你就能够为所欲为的修改这些值了(本子是你的,随便画均可以),除非你保存这些数据变化,不然持久层的东西是不会变 的。(跟修改文件后要保存是一个道理)。
附在Core Data框架中模型对象(Model objects)常被称为“被管理对象”(Managed objects)。全部的被管理对象都要经过上下文进行注册。使用上下文,你能够在对象图中添加、删除对象,并记录对象的更改(包括单个对象,或是对象间 的关系)。记录更改后就能支持撤销和重作的功能。同时,上下文还能保证关系更改后对象图的完整性。
若是你想要保存所作的修改, 上下文会保证对象的有效性。在验证有效性后,更改会被写入到persistent store(持久化存储层)中。你在程序中的添加和删除动做都会被做用在存储的数据中。
在你的一个程序中,可能存在多个上下文。 对于数据存储(store)中的每一个对象,对应的都有惟一的一个被管理对象(managed object)和上下文相关联(详情请查看"Faulting and Uniquing"文档)。换个角度来想,在persistent store中存储的对象有可能被用在不一样的上下文中,每一个上下文都有与之对应的被管理对象,被管理对象能够被独立的修改,这样就可能在存储 时致使数据的不一致。Core Data提供了许多解决这个问题的途径(请查看"Using Managed Object"一章)。
获取数据的请求(Fetch Requests)
要使用上下文来获取数据,你须要建立相应的请求(Fetch request)。 Fetch request对象包含你想获取的对象的描述。例如:“全部 Employee”,或“全部的Employee,department是marketing,按薪资降序排列”。Fetch Request包含三个部分。使用最简单的写法,必须指定实体(Entity)的名称,这就暗示了,每次智能得到一种类型的实体。 Fetch Request 还能够包含谓词(predicate)——注:有些地方也把这个叫断言,我的感受谓词更准确些。谓词将描述对象须要知足的条件(这就和咱们在SQL里加的 限定条件差很少,正如前面的"All Employees, in the Marketing department")。另外,Fetch Request还可包含一个用于描述排序方式的对象(熟悉的Order by操做)。如图Figure3所示:
Core Data追求高执行效率。 它是“需求驱动”的,所以只会建立你确实须要的对象。对象图不须要保留全部在数据存储层中的对象。单纯指定数据持久层的动做不会将其中全部的数据放到上下 文中去。 当你想从数据存储层中获取某些对象的时候,你只会获得那些你请求的(有点罗嗦,总的意思就是须要时获取,获取的就是须要的)。若是你不在须要这个对象的时 候,默认状况下它会被释放。(固然,只是释放这个对象,而不是从对象图中移除该对象)。——注:我的感受有点像从新拷了一个文件的某些部分,不用了就在副 本中删除,不会影响原件。
持久化存储助理(Persistent Store Coordinator)
以前提到过,程序中的对 象和外部存储的数据经过Core Data框架中的一系列对象进行协调,这一系列的对象总的被称为持久存储栈(Persistence stack)。在栈顶是被管理对象上下文(Managed object context),而栈底是持久化对象存储层(Persistence object store)。在它们之间就是持久化存储助理。
事实上,持久化存储助理定义了一个栈。从设计方面考虑,它就是能够做为上下 文的”外观“, 这样多个数据存储(Persistence store)看起来就像是一个。 而后上下文就能够根据这些数据存储来建立对象图了。持久化存储助理智能关联一个被管理对象的模型。若是你像要把不一样的实体放到不一样的存储中去,就须要为你 的模型实体作“分区”,方式是经过定义被管理对象模型的configurations。(请参考"Configurations"一章)。
Figure 4演示了这样的一个结构:employees和departments存储在一个文件中,customers和companies存储在另一个文件中。当你要获取对象的时候,它们从相关的文件中自动获取;当保存时,又被归档到相应的文件中。
Figure 4存储栈—改
持久化存储(Persistent Stores)
持久化存储是和单独的一个文件或外部的数据关联的,它负责将数据和上下文中的对象进行对应。一般,须要你直接和持久化对象存储打交道的地方,就是指定新 的、 和程序进行关联的外部数据的位置(例如,当用户打开或保存一个文档)。大多数须要访问持久化存储的动做都由上下文来完成。
程序的代码—— 特别是和被管理对象相关的部分——不该该对持久化存储作任何假设(也就是不须要本身考虑存储的方式或过程)。 Core Data对几种文件格式有原生的支持。你能够选择一种本身程序须要的。假设在某个阶段你决定换一种文件的格式,而又不想修改程序的框架,并且,你的程序作 了适当的抽象(注:这个就属于设计方面的东东了),这时,你就能尝到使用Core Data的甜头了。例如,在最初的设计中,程序只从本地文件中获取数据,而你的程序没有去硬指定对应数据的获取位置,而是能够在后期指定从远程位置添加新 的数据类型,这样你就可使用新的类型,而不须要修改代码。(这段仍是感受翻的不太合适)。
重要提示:
虽然Core Dta支持SQLite做为一种存储类型,但它不能使用任意的SQLite数据库。Core Data在使用的过程种本身建立这个数据库。(详情,请参考"Persistence Store Features")。
持久化文档(Persistent Documents)
你能够经过代码的方式建立和配置持久存储栈,但在多数状况下,你只是想建立一个基于文档 的应用程序(Document-based application,这个是mac上的)来读写文件。这时,用NSDocument的子类NSPersistentDocument可让你感觉到使 用Core Data的便利。默认情况下,NSPersistentDocument就已经建立了它本身的持久存储栈,其中包含了上下文,和单个的持久对象存储,来处 理这样文档和外部数据“一对一”的映射关系。
NSPersistentDocument类提供了访问文档的上下文的方法,也实现了标准的NSDocument方法来经过Core Data读写文件。 通常说来,你不须要编写额外的代码来处理对象的持久化。
持久化文档的撤销(undo)操做也被集成在被管理对象的上下文中。
被管理对象和被管理对象模型(Managed Objects and the Managed Object Model)
为 了管理对象图,也为了提供对象持久化的功能,Core Data须要对对象有很强的描述能力。被管理对象模型就是程序中对象、实体描述的概要图,如图Figure 5所示。建立模型的经常使用作法是经过Xcode的图形化建模工具Date Model Design tool。可是若是你愿意的话,也能够在运行时经过代码来建模。
Figure 5 有两个实体的对象模型
Figure 6 带有两个属性和一个关系的的实体描述
被管理对象模型(Managed Object Models)
多数Core Data的功能依赖于你建立的,用来描述程序的实体及其属性、关系的模型图。 模型图由NSManagedObjectModel所表示。通常说来,模型的信息越充实,Core Data能提供的功能就越好。 下文讲解了对象模型的特性,以及如何在程序中建立、使用对象模型。
被管理对象模型的特性
被管理对象模型是 NSManagedObjectModel的实例。它描述了你在程序中使用的实体的概要信息。(若是读者不了解entity、property、 attribute和relationship的含义,请先查看"Core Data Basics"和"Cocoa Design Patterns"文档中的"Object Modeling"一节)
实体(Entities)
模型包含了NSEntityDescription对象,NSEntityDescription对象指代了模型的实体。关于实体由两个重要特征:名称 (name)和类名(name of class)。你应该弄清楚实体、实体的类和做为实体实例的被管理对象之间的区别。
NSEntityDescription 对象可包含NSAttributeDescription对象(指代实体的attribute)和NSRelationshipDescription对 象(指代实体间的relationship)。实体也可能包含fetched属性,该属性由NSFetchedPropertyDescription指 代,模型中有对应的fetch请求的模板,fetch请求由NSFetchRequest所指代。
实体的继承关系
实体的继承和类 的继承很相似,固然,也一样有用。 若是你有若干个类似的实体,就能够抽离出它们的共有特性做为一个“父实体”,就省去了在多个实体中都指定相同的属性。 例如,你能够定义一个包含firstName和lastName的“Person”实体,而后在定义子实体"Employee"和"Customer"。
若是是使用Xcode的可视化建模工具来建立模型,你就能够经过以下图的方式为一个实体指定父级实体。
Figure1 Xcode中为一个实体指定父实体
抽象实体
你能够把一个实体指定为“抽象实体”,也就是说,你不打算使用这个实体来建立实例。一般,当你想把这个实体做为父实体,而有子实体来实现详细内容的时候, 就 把它声明“抽象实体”。(和抽象类很像)。例如,在一个绘图程序中,你可能会设计一个Graphic实体,它包含了x和y坐标信息、颜色、绘制区域,而你 不会去建立一个Graphic的实例,而是使用具体的子实体——Circle、TextArea、Line。(这些基本的东西就不给大牛们再罗嗦 了。。。)
Properties(属性,这个和Attributes的意思同样,实在区别不出来,只好上英语了)
实体的 Properties是它的attributes和relationship,包含了fetched属性(若是有的话)。每一个property都有名称和 类型。 Attribute也可能有默认值。property的名称不能和NSObject和NSManagedObject类中的无参方法名相同。例如,不能把 property命名为"description"。
临时属性(Transient Property)也是做为模型的一部分,可是不做为实体实例的数据保存在持久存储层。 Core Data也会跟踪临时属性的变化,以备撤销操做时使用。
注意:若是你用模型外的信息对临时属性执行撤销操做,Core Data将不会使用旧值,调用你的set方法——它只会更新快照信息(snapshot information)。(这段怪怪的,用到的话在修改一下翻译吧)
Attributes
Core Data内部支持各类attribute的类型,例如string,date,integer(NSString, NSDate, NSNumber)。若是你使用那些不支持的数据,你须要用到在“Non-Standard Persistent Attributes”介绍到的技术。
你能够将一个attribute声明为“可选”(optional),可选的attribute不 必须有值,可是,不鼓励你将属性置空——尤为是数字值(更好的解决方案是使用强制的值,在这里,咱们用默认值,例如0)。 这样作的缘由是为了配合SQL中对于空值NULL作比较的操做:NULL不一样于Objective-C中的nil。 数据库中的NULL不一样于0,搜索0值的操做不会匹配到值为NULL的列。
false == (NULL == 0)
false == (NULL != 0)
并且,在数据库中,NULL也不等于空字符串或是空的数据对象:
false == (NULL == @"")
false == (NULL != @"")
它们之间一点关系都没有。
关系(Relationships)
Core Data支持对1、对多的关系,也支持fetched属性。 Fetched property表示了一种“弱”的、单项的关系。 在employees和departments的例子中, department 的一个fetched property多是“最近雇佣人”(recent hires),而反过来,employee不会拥有这样的关系。
获取数据请求的模板(Fetch Request Templates)
咱们使用NSFetchRequest类来描述数据请求,利用数据请求从持久存储(persistent store)中获取对象。 常常须要屡次执行一样的请求,或是执行某种模式的请求,可是其中包含可变的元素(如查找条件)——这些元素常常有用户提供。 例如,在运行的时候,你要根据用户须要获取某个做者在某个指定日期后的出版的全部出版物。
你能够预约义请求,把它们做为模板存储在被管理对象模型中。 预约义的模板在你须要的时候就能够取出使用。一般状况下,咱们经过Xcode的data modeling tool工具建立请求模板。模板能够包含变量,如图Figure 2所示。
Figure 2 Xcode predicate builder
关于Fetch request templates的详细信息,请查看"Accessing and Using a Managed Object Model at Runtime"的描述。
用户信息字典(User Info Dictionaries)
模型中的许多元素,诸如entities, attributes, relationships,都有相关的用户信息字典。用熟悉的键-值对,你能够向其中放置任何你须要的数据。这里经常使用的信息有实体的版本详情,还有针对 fetched property,给谓词(predicate)用的值。
配置(Configurations)
配置包含了一个名称和若干个相关的实体。实体的集合是能够重叠的——这就是说,一个实体能够出如今多个配置中。在代码中,咱们使用 setEntities: forConfiguration:的方法来指定配置。也能够用Xcode的建模工具来指定(选中某个实体,就在属性窗口的第三个,就是一个小扳手的符 号)。要获取某项配置的实体,须要用entitiesForConfiguration:的方法。
通常说来,若是你想把不一样的实体存放在不一样的存储中去,就可能用到配置。一个持久化存储助理(persistent store coordinator)只能有一个被管理对象模型。因此,默认状况下,和助理关联的某个存储必须包含一样的实体。要想绕过这个限制,你能够建立一个包含 实体子集的模型,而后为每个子集建立配置,这样一来,使用这个模型建立助理,当你须要添加存储时,可以使用不一样的配置指定对应的存储属性。当你建立配置的 时候,须要记住,不能建立跨存储的关系。
使用被管理对象模型
一般可使用Xcode的建模工具来建立模型(请参考"Create a managed object with Xcode")。你也能够所有使用代码来建立(请参考"Core Data Utility Tutorial")。
编译数据模型
数据模型是一种部署资源。 在模型中,除了有实体和属性的详细信息外,用Xcode建立的模型还包含了一些额外的视图信息,包括布局、颜色等等。这些信息在运行时不是必须的。模型文 件在编译的过程当中会删除这些额外信息以保证尽量高效的加载。xcdatamodel“源”文件会被momc编译器编译为mom的目标文件。
"mom" 位于 /Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/,若是你想把它用在本身的 build脚本中,格式是:mom source destination, source 就是Core Data Model文件,destination就是输出的mom文件。
加载数据模型
在一些状况下,你不须要写任何加载模型的代码。若是你使用基于文档的程序框架(Document-based application),NSPersistentDocument会管理诸如查找模型、加载模型的任务。 若是你建立了非Document-based application,并且里面又用到了Core Data,通常将获取模型的代码放在application delegate里。模型的存储名称——也就是文件名,
和运行时的名称是不相关的,一旦模型被加载,文件名就没有什么意义了。也就是说,对模型文件,你能够随意命名。
若是你想手动加载模型,有两种方式可用,它们各有各的好处:
你能够从指定的bundle集合里建立整合模型,使用以下的类方法:
mergeModelFromBundles:
也能够用指定的URL加载单个的模型,使用以下的实例方法:
initWithContentsOfURL: (这个方法相信你们都用过)
若不须要考虑分开加载模型,第一个类方法很适用。例如:在你的程序中和程序连接的framework里都有你想要加载的模型。这个类方法可让你很轻松的加载全部的模型,而不须要考虑模型文件的名称,也不用特定的初始化方法来保证全部的模型都被找到。
可是当你有多个模型要加载,特别是这些模型都表明了一个schema的不一样版本,这时,知道要加载哪一个模型就很重要了(合并包含相同实体的模型可能致使命 名冲突和错误,咱们以前“一锅端”的方法不太合适了)。在这种状况下,咱们能够用第二个实例方法。 另外,有时咱们也须要将模型存储在bundle以外,也须要用这个方法从指定的URL位置加载模型。
还有一点须要说明:咱们还有一个类方法 modelByMergingModels:能够用。像mergedModelFromBundles:方法同样,它也能合并给定的若干个模型。这样,我 们就能够经过URL来逐一加载模型,而后在建立助理对象以前将它们整合为一个。
改变模型
因为模型描述了存储层数据的结构,任何改变模型的动做都将使其不在适配于以前建立的存储层。 若是你改变了模型的结构,就须要将当前存储层的数据迁移到新版本。(请参考"Core Data Model Versioning and Data Migration Programming Guide"文档)。例如:若是你添加了新的实体,或新的属性,你将没法打开旧的存储;若是你添加了验证的限制,或者为属性添加了新的缺省值,你就能够打 开旧的存储。
在运行时访问和适用被管理对象模型
在运行时,被管理对象模型就是一个简单的“对象图”(这个概念以前提到过),认识到这点很重要,尤为是当你须要用代码来访问模型的详细信息时。例如:修改 模型(你只能在runtime以前这样作,请参考 NSManagedObjectModel),取回信息(如本地化实体名,属性数据类型,或数据请求模板)。
在运行时访问模型有不少方法,经过持久栈最终从持久化存储助理获得模型,代码以下:
[[aManagedObjectContext persistentStoreCoordinator]managedObjectModel];
你也能够经过实体描述获得模型,所以给定一个被管理对象,你就能够获得它的实体描述,进而得到模型。代码以下:
[[aManagedObject entity] managedObjectModel];
某些状况下,你要维护模型的“直接”引用,也就是说,一个直接返回模型的方法。NSPersistentDocument提供了 managedObjectModel方法,能够返回一个模型,该模型和在文档的上下文中使用的持久化存储助理相关联。若是你使用Core Data Appplication的模板,application delegate将负责模型的引用。
经过代码建立获取数据请求模板(Fetch Request Templates)
你能够经过代码建立数据请求模板并将其和模型关联,方法是:setFetchRequestTemplate: forName:如Listing-1所示。 提醒一下:你只能在模型被助理(coordinator)使用以前修改它。
Listing 1 经过代码建立获取数据请求模板
NSManagedObjectModel *model = …;
NSFetchRequest * requestTemplate = [[NSFetchRequest alloc]init];
NSEntityDescription *publicationEntity =
[[model entitiesByName] objectForKey: @"Publication"];
[requestTemplate setEntity: publicationEntity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
@"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate: predicateTemplate];
[model setFetchRequestTemplate: requestTemplate
forName: @"PublicationForAuthorSinceDate"];
[requestTemplate release];
访问请求模板
你能够用"Accessing and Using a Managed Object Model at Runtime"里介绍的代码片断来获取并使用请求模板。替换字典必须包含和模板中定义的变量对应的键。若是你想测试null值,必须使用NSNull对 象——参考"Using Predicates"。(注:这里的替换字典很好理解,以前的模板中用到了诸如$FIRST_NAME, $LAST_NAME, $DATE这些东西,就至关于咱们在模板中建立好的“变量”,咱们须要把一个模板“具体化”,就用替换字典,将里面的变量对应一个值,这里看代码就明白 了。)
NSManagedObjectModel *model = …;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
@"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
[NSDate dateWithTimeIntervalSinceNow: -31356000], @"DATE", nil]; //这里的FIRST_NAME, LAST_NAME, DATE和咱们以前模板里的$FIRST_NAME, $LAST_NAME和$DATE对应
NSFetchRequest *fetchRequest =
[model fetchRequestFromTemplateWithName: @"PublicationForAuthorSinceDate"
substitutionVariables: substitutionDictionary]; //从以前的model中拿出请求模板,而后设定替换字典
NSArray *results =
[aManagedObjectContext executeFetchRequest: fetchRequest error: &error];
要是模板里不包含可替换的变量,你要么
1. 使用fetchRequestFromTemplateWithName: substitutionVariables: 方法,传递nil给第二个参数
或者:
2. 使用fetchRequestTemplateForName: 并将结果copy。这个方法不须要传递“替换变量”这个参数,可是若是你要用返回值自己,将会有异常抛出(没法在不可变的模型中修改命名的数据请 求"Can't modify named fetch request in an immutable model")。
本地化被管理对象模型
你能够对模型的大部份内容作本地化处理,包括实体和属性名,还有错误信息。要明白,“转成你本身的语言”也是本地化的一部分。 即便你不打算提供外语版本, 显示“天然语言”的出错提示信息也会有更好的用户体验。例如:“First Name is a required property”就比"firstName is a required property"更好。(后面的这个更像是开发者用的log,显示的是变量名,这里不太明显)。
要想对模型进行本地化处理,须要提供一个本地化字典,模式以下:
Table 1 针对被管理对象模型的本地化字典键值对应关系:
备注:(1)在不一样实体中的属性,拥有相同的原始名称,但须要不一样的本地化名称,适用于该格式。
咱们能够经过localizationDictionary方法来访问本地化字典。注意:在Mac OS X 10.4上,这个方法可能返回nil,除了Core Data为了某些特定目的(如报告本地化的错误描述)延迟加载本地化字典。
字符串文件
处理模型的本地化最简单的方法就是建立对应的字符串文件——字符串文件名和模型文件名一直,可是后缀名用.strings。(例如,模型文件名为 MyDocument.xcdatamodel,对应的字符串文件名就为MyDocumentModel.strings;若是模型文件已经包含了 Model后缀,你必须再附加一个Model,因此,若是模型文件名为JimsModel.xcdatamodel对应的字符串文件名为 JimsModelModel.strings)。字符串文件格式和标准字符串文件相似(请参考"Localizing String Resources"),可是对应的键值要遵循Table-1中的规则。
一个模型的字符串文件实例:
更详细的示例请参考"NSPersistentDocument Core Data Tutorial"。
代码实现设置本地化字典
你能够在运行时设定本地化字典,适用NSManagedObjectModel的setLocalizationDictionary:方法便可。你必须 建立一个符合Table-1格式的字典,并把它和模型关联。必须保证在模型被使用(获取或建立被管理对象)以前作这些工做,由于再使用后模型就不可编辑 了。 Listing 3演示了建立包含本地化字典的被管理对象模型。实体名称叫“Run”,它有两个属性: "date"和"processID",分别是date和integer类型。process ID的值不能为负。
Listing 3 经过代码建立被管理对象模型
这段代码写的比较多,这里再也不解释了。本地化字典的代码在最后。建立一个符合格式的localizationDictionary,而后用model调用便可。
-----------------------------------------------------------------------------------
在接触到CoreData时,感受就是苹果封装的一个ORM。CoreData负责在Model的实体和sqllite创建关联,数据模型的实体类就至关于Java中的JavaBean, 而CoreData的功能和JavaEE中的Hibernate的功能相似,最基本是二者都有经过对实体的操做来实现对数据库的CURD操做。CoreData中的上下文(managedObjectContext)就至关于Hibernate中的session对象, CoreData中的save操做就和Hibernate中的commit,还有一些类似之处,在这就不一一列举了。(上面是笔者本身为了更好的理解CoreData而作的简单类比,若是学过PHP的ThinkPHP框架的小伙伴们也能够和TP中的ORM类比)。
那么TableView为何会爱上CoreData呢?下面会通个代码给出他们相爱的缘由。就举一个IOS开发中的经典的demo:通信录来讲明问题。
1.在TableView没遇到CoreData的时候咱们怎么经过动态表视图来显示咱们的通信录的内容呢?也就是说咱们通信录的数据结构该如何组织呢?
为了在TableView中显示咱们的信息咱们这样设计咱们的数据结构:
1.整个TableView是一个可变的数组tableArray;
2.tableArray中的每一个元素又是一个存放分组的字典sectionDictionary;
3.在sectionDictionary中咱们存放着两个键值对 header和items, header中存放的时section中的名字,items中存放的时每一个section中的用户信息
4.items中又是一个数组rowsArray, rowsArray中存放的又是一个字典userInfoDictionary, 在userInfoDictionary中存放着咱们要显示的信息
千字不如一图,看到上面对咱们要设计的数据结构的描述会有点迷糊,下面来张图吧:
2.数据结构咱们设计好了,那么如何用代码生成咱们的测试数据(数据的组织形式如上图所示),下面的代码就是生成咱们要在tableView中显示的数据,生成的数组存储在tableArray中,代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
/*
*手动建立咱们在动态表视图上显示的数据格式
*整个数据存储在一个数组中
*数组中每个元素是一个自动,字典的key是sectionHeader的值,value是该section中以数组形式存的数据
*section中的每一行对应着一个数组元素,数组元素中又存储着一个字典,字典中存储着用户的具体数据。
*/
//为咱们的数组分配存储空间, 表明着有20个section
self.telBook = [NSMutableArray arrayWithCapacity:26];
//为咱们的section设置不一样的header
char
header =
'A'
;
//计数
static
int
number = 0;
for
(
int
i = 0; i < 26; i ++) {
//新建字典来存储咱们每一个section中的数据, 假设每一个section中有1个数组
NSMutableDictionary *sectionDic = [NSMutableDictionary dictionaryWithCapacity:1];
//建立字典中的数组,数组中以键值对的形式来储存用户的信息
NSMutableArray *rowArray = [NSMutableArray arrayWithCapacity:3];
for
(
int
j = 0; j < 3; j ++)
{
//建立存储用户信息的字典
NSMutableDictionary *user = [NSMutableDictionary dictionaryWithCapacity:2];
//生成测试数据
NSString *name = [NSString stringWithFormat:@
"User%03d"
, number];
NSString *tel = [NSString stringWithFormat:@
"12345%03d"
, number++];
//加入字典中
[user setObject:name forKey:@
"name"
];
[user setObject:tel forKey:@
"tel"
];
//把字典加入数组
[rowArray addObject:user];
}
//把rowArray添加到section字典中
NSString *key = [NSString stringWithFormat:@
"%c"
,(header+i)];
[sectionDic setObject:key forKey:@
"header"
];
[sectionDic setObject:rowArray forKey:@
"items"
];
//把section添加到总的数组中
[self.telBook addObject:sectionDic];
}
|
3.把咱们用代码建立的模拟数据在咱们的TableView中进行显示,在相应的函数中根据咱们生成的数据返回相应的值显示在TableView中,显示代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#pragma mark - Table view data source
//返回Section的个数,即咱们telBook数组元素的个数
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return
self.telBook.count;
}
//返回每一个section中的行数,即section中的数组元素的个数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *rowArray = self.telBook[section][@
"items"
];
return
rowArray.count;
}
//给每一个分组设置header
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
//获取每一个section中的header
NSString *title = self.telBook[section][@
"header"
];
return
title;
}
//获取cell并添加完数据发挥
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@
"Cell"
forIndexPath:indexPath];
//获取secion中的数据数组
NSArray *items = self.telBook[indexPath.section][@
"items"
];
//获取数组中的每一项的一个字典
NSString *name = items[indexPath.row][@
"name"
];
NSString *tel = items[indexPath.row][@
"tel"
];
//给sel设置值
cell.textLabel.text = name;
cell.detailTextLabel.text = tel;
return
cell;
}
|
4.上面给出的时关键代码,至于怎么配置TableView的Cell模板或者如何把TableViewController和Storyboard中的ViewController绑定,在前面的博客中都有介绍,在这小编就不作赘述。运行结果和上面的图片是同样的。
上面的东西只是这篇博文的引子,为了显示上面的数据结构咱们这样作是否是太麻烦了,并且上面的数据是不能被持久化存储的。若是给咱们的数据都要转换成上面的数据组织形式,想必因为所给数据结构的不肯定,因此转换起来是至关的复杂的。TableView之因此会爱上CoreData,是由于咱们的CoreData会简化咱们对数据的操做,而且会持久化到sqlite中。CoreData至关于TableView和sqllite的纽带,说的专业一些就是映射,那么咱们CoreData如何使用才会简化咱们的操做呢?下面将要介绍的才是这篇博客中的重点:咱们如何使用CoreData才会让TableView爱上它呢?
1.新建一个Empty Application, 在新建工程的时候,不要忘了把Use Core Data给选中,选中Use Core Data会自动引入Core Data框架库和在AppDelegate.h和AppDelegate.m中进行相应的配置,而且同时还自动生成一个以本应用名命名的Data Model文件,咱们能够在Data Model文件中添加咱们的数据模型, 添加好的数据模型咱们会在生成数据实体类时使用(和JavaBean相似)
(1)AppDelegata.m中多出的部分代码以下,从多出的部分代码就能够看出,CoreData会把咱们的数据实体和sqllite创建起一一对应的关系:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
if
(_managedObjectModel != nil) {
return
_managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@
"Demo083101"
withExtension:@
"momd"
];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return
_managedObjectModel;
}
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if
(_persistentStoreCoordinator != nil) {
return
_persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@
"Demo083101.sqlite"
];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if
(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(@
"Unresolved error %@, %@"
, error, [error userInfo]);
abort
();
}
return
_persistentStoreCoordinator;
}
|
(2)咱们能够经过 projectName.xcdatamodeld中建立咱们的数据实体模型,以下图所示
(3)经过建立好的数据实体模型来建立咱们的实体类(和JavaBean相似的东西)建立过程以下图,点击下一步之后,选中建立的实体模型便可:
2.CoreData准备的差很少啦,该咱们的TableView出场啦,在Empty Application中默认的时没有storyboard, 若是你又想经过storyboard来简化你的操做,得给应用建立一个storybaord才对,建立过程以下:
(1)第一步建立一个storyboard文件,命名为Main,以下图所示
(2)第二步:设置从storyboard来启动, 在Main InterFace中选中咱们建立的storyboard便可
(3) 第三步修改AppDelegate.m中的函数以下所示,把初始化的工做交给咱们建立的storyboard进行:
1
2
3
4
|
- (
BOOL
)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return
YES;
}
|
3.配置工做完成接下来就是TableView和CoreData相爱的过程啦,如何在storyboard中对TableView的cell进行配置在这儿就不赘述了,下面给出咱们要经过TableView和CoreData来实现什么功能。
(1)咱们要实现对通信录的增删改查,主要需求入下图所示:
(2)实现添加功能,点击右上角的添加按钮时会跳转到添加页面,在添加页面中有两个TextField来接受用户的输入,点击添加按钮进行数据添加。AddViewController.m中的主要代码以下。
a.须要用到的属性以下, 用NSManagedObejectContext的对象来操做CoreData中的数据,和Hibernate中的session的对象类似
1
2
3
4
5
|
@property (strong, nonatomic) IBOutlet UITextField *nameTextField;
@property (strong, nonatomic) IBOutlet UITextField *numberTextField;
//声明CoreData的上下文
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
b.获取UIApplication的单例application, 而后再经过application获取delegate, 最后经过delegate来获取上下文,代码以下:
1
2
3
4
|
//经过application对象的代理对象获取上下文
UIApplication *application = [UIApplication sharedApplication];
id delegate = application.delegate;
self.managedObjectContext = [delegate managedObjectContext];
|
c.编辑点击button要回调的方法,在点击添加按钮时首先得经过上下文获取咱们的实体对象,获取完实体对象后再给实体对象的属性赋上相应的值,最后调用上下文的save方法来存储一下咱们的实体对象。添加完之后还要经过navigationController来返回到上一层视图,代码以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- (IBAction)tapAdd:(id)sender {
//获取Person的实体对象
Person *person = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Person
class
]) inManagedObjectContext:self.managedObjectContext];
//给person赋值
person.name = self.nameTextField.text;
person.number = self.numberTextField.text;
person.firstN = [NSString stringWithFormat:@
"%c"
, pinyinFirstLetter([person.name characterAtIndex:0])-32];
//经过上下文存储实体对象
NSError *error;
if
(![self.managedObjectContext save:&error]) {
NSLog(@
"%@"
, [error localizedDescription]);
}
//返回上一层的view
[self.navigationController popToRootViewControllerAnimated:YES];
}
|
(3)实现上面的代码只是经过CoreData往sqlite中添加数据,要想在咱们的TableView中显示还须要经过CoreData把咱们的存储在sqlite中的数据来查询出来,再用CoreData给咱们提供的方法把查询结果作一个转换,转换成适合TableView显示的数据,下面给出相应的获取数据的代码。
a.在TableViewController咱们须要声明以下两个属性,一个用于获取上下文,一个用于存储返回结果
1
2
3
4
|
//声明经过CoreData读取数据要用到的变量
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
//用来存储查询并适合TableView来显示的数据
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
|
b.在viewDidLoad中获取上下文
1
2
3
4
|
//经过application对象的代理对象获取上下文
UIApplication *application = [UIApplication sharedApplication];
id delegate = application.delegate;
self.managedObjectContext = [delegate managedObjectContext];
|
c.在viewDidLoad中经过上下文来查询数据,并存储在fetchedResultsController中, 在获取数据的过程当中咱们须要定义UIFetchRequest 和排序规则,代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/*********
经过CoreData获取sqlite中的数据
*********/
//经过实体名获取请求
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Person
class
])];
//定义分组和排序规则
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@
"firstN"
ascending:YES];
//把排序和分组规则添加到请求中
[request setSortDescriptors:@[sortDescriptor]];
//把请求的结果转换成适合tableView显示的数据
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:@
"firstN"
cacheName:nil];
//执行fetchedResultsController
NSError *error;
if
([self.fetchedResultsController performFetch:&error]) {
NSLog(@
"%@"
, [error localizedDescription]);
}
|
d.把查询到的数据显示在TableView中代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
//咱们的数据中有多少个section, fetchedResultsController中的sections方法能够以数组的形式返回全部的section
//sections数组中存的是每一个section的数据信息
NSArray *sections = [self.fetchedResultsController sections];
return
sections.count;
}
//经过获取section中的信息来获取header和每一个secion中有多少数据
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *sections = [self.fetchedResultsController sections];
//获取对应section的sectionInfo
id<NSFetchedResultsSectionInfo> sectionInfo = sections[section];
//返回header
return
[sectionInfo name];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *sections = [self.fetchedResultsController sections];
id<NSFetchedResultsSectionInfo> sectionInfo = sections[section];
//返回每一个section中的元素个数
return
[sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@
"Cell"
forIndexPath:indexPath];
//获取实体对象
Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = person.name;
cell.detailTextLabel.text = person.number;
// Configure the cell...
return
cell;
}
|
(4) 经上面的代码,咱们就能够经过CoreData查询sqlite, 而后把查询测数据结果显示到TableView中,但是上面的代码有个问题,就是当经过CoreData来修改或着添加数据时,TableView上的内容是不跟着CoreData的变化而变化的,接下来要作的就是要绑定TableView和CoreData的关系。即经过CoreData修改数据的同时TableView也会跟着改变。
a.要想实现TableView和CoreData的同步,咱们须要让TableView对应的Controller实现协议NSFetchedResultsControllerDelegate, 而后再ViewDidLoad中进行注册,在添加上相应的回调代码便可。实现协议的代码以下:
1
2
3
4
5
|
#import <UIKit/UIKit.h>
@interface MyTableViewController : UITableViewController<NSFetchedResultsControllerDelegate>
@end
|
b.进行委托回调的注册,在viewDidLoad中添加
1
2
|
//注册回调,使同步生效
self.fetchedResultsController.delegate = self;
|
c.添加相应的委托回调的方法,咱们能够到Help中的API中去复制, 查询NSFetchedResultsControllerDelegate,找到相应的回调代码复制过来而后再作简单的修改便可, 实现回调的方法代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
/*
Assume self has a property 'tableView' -- as is the case for an instance of a UITableViewController
subclass -- and a method configureCell:atIndexPath: which updates the contents of a given cell
with information from a managed object at the given index path in the fetched results controller.
*/
//当CoreData的数据正在发生改变是,FRC产生的回调
- (
void
)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
//分区改变情况
- (
void
)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch
(type) {
case
NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break
;
case
NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break
;
}
}
//数据改变情况
- (
void
)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch
(type) {
case
NSFetchedResultsChangeInsert:
//让tableView在newIndexPath位置插入一个cell
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break
;
case
NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break
;
case
NSFetchedResultsChangeUpdate:
//让tableView刷新indexPath位置上的cell
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break
;
case
NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break
;
}
}
//当CoreData的数据完成改变是,FRC产生的回调
- (
void
)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
|
(5)通过上面的代码就能够实现CoreData和TableView的同步啦,到此会感受到TableView结合着CoreData是如此的顺手,虽然配置起来较为麻烦,但仍是比较中规中矩的,只要循序渐进的来,是不难实现的。所以TableView深爱着CoreData. 上面咱们完成了经过CoreData来对数据的插入和查询并同步到TableView中,下面将会介绍到如何对咱们的Cell进行删除。
a.想经过TableView来删除数据的话得开启咱们的TableView的编辑功能
1
2
3
4
5
6
7
|
//开启编辑
// Override to support conditional editing of the table view.
- (
BOOL
)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return
YES;
}
|
b.开启编辑功能之后咱们就能够在tableView的对应的方法中来实现删除功能啦,当点击删除时,咱们需呀获取cell对应的索引在CoreData中的实体对象,而后经过上下文进行删除,在save一下便可。由于CoreData和TableView已经进行了同步,因此删除后TableView会自动更新,删除代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// Override to support editing the table view.
- (
void
)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if
(editingStyle == UITableViewCellEditingStyleDelete)
{
//经过coreData删除对象
//经过indexPath获取咱们要删除的实体
Person * person = [self.fetchedResultsController objectAtIndexPath:indexPath];
//经过上下文移除实体
[self.managedObjectContext deleteObject:person];
//保存
NSError *error;
if
([self.managedObjectContext save:&error]) {
NSLog(@
"%@"
, [error localizedDescription]);
}
}
}
|
c.默认的删除按钮上显示的是Delete, 能够经过下面的方法进行修改,代码以下:
1
2
3
4
5
6
|
//设置删除的名字
-(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
{
return
@
"删除"
;
}
|
(6)到这一步删除功能算是完成了,还有最后一个功能点,就是更新咱们的数据。更新数据经过点击相应的cell,把cell上的数据传到UpdateView的页面上,而后进行更新便可。
a.下面的代码是获取数据咱们选中的数据并经过KVC把参数传到目的视图中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#pragma mark - Navigation
//把对应的cell上的值传到修改的页面上
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (
void
)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//参数sender是点击的对应的cell
//判断sender是否为TableViewCell的对象
if
([sender isKindOfClass:[UITableViewCell
class
]]) {
//作一个类型的转换
UITableViewCell *cell = (UITableViewCell *)sender;
//经过tableView获取cell对应的索引,而后经过索引获取实体对象
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//用frc经过indexPath来获取Person
Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
//经过segue来获取咱们目的视图控制器
UIViewController *nextView = [segue destinationViewController];
//经过KVC把参数传入目的控制器
[nextView setValue:person forKey:@
"person"
];
}
}
|
b.在UpdateViewController中把传过来的实体对象进行更新,再保存。更新部分的代码和添加部分的代码差很少,在这就不往上贴啦。
通过上面的艰苦的历程后咱们的tableView就会深深的爱上CoreData, 可能上面的内容有些多,有疑问的能够留言交流。
上面所作的功能里咱们的真正的通信录还有些差距,看过上面的代码的小伙伴会有个疑问:添加的页面和更新的页面能不能使用同一个呢? 固然啦,为了遵循Don`t Repeat Yourself的原则,下面咱们就把两个类似的页面合并在一块儿,同时给咱们每条记录加上头像和给整个tableView加上索引。
1.把更新页面删掉,作以下修改,点击添加和修改都跳转到咱们的编辑页面,同时添加一个自定义Button,点击Button时,咱们会调用ImagePickerController来从手机相册获取图片:
2.为了把头像持久化存储,咱们还得修改数据模型,重新生成Person类,添加一个存储image的选项,是经过二进制的形式存储的
3.在以前保存的ViewController中若是Person为空,说明是执行的添加记录的方法咱们就生成一个新的person, 若是Person不为空则不新建Person对象,直接更新完保存。
(1)为了获取图片,咱们须要添加ImagePickerController对象,并在viewDidLoad中作相应的配置,代码以下
1
2
|
//声明ImagePicker
@property (strong, nonatomic) UIImagePickerController *picker;
|
进行相关配置
1
2
3
4
5
6
|
//初始化并配置ImagePicker
self.picker = [[UIImagePickerController alloc] init];
//picker是否能够编辑
self.picker.allowsEditing = YES;
//注册回调
self.picker.delegate = self;
|
(2)点头像会跳转到咱们定义好的ImagePickerController中,咱们就可在图片库中选取相应的照片啦。
1
2
3
4
5
6
7
|
//点击图片按钮设置图片
- (IBAction)tapImageButton:(id)sender {
//跳转到ImagePickerView来获取按钮
[self presentViewController:self.picker animated:YES completion:^{}];
}
|
(3)在ImagePickerController中点击取消按钮触发的事件,跳转到原来编辑的界面
1
2
3
4
5
6
|
//回调图片选择取消
-(
void
)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
//在ImagePickerView中点击取消时回到原来的界面
[self dismissViewControllerAnimated:YES completion:^{}];
}
|
(4)选完图片把头像设置成用户选中的按钮,并dismiss到原来界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//实现图片回调方法,从相册获取图片
-(
void
) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
//获取到编辑好的图片
UIImage * image = info[UIImagePickerControllerEditedImage];
//把获取的图片设置成用户的头像
[self.imageButton setImage:image forState:UIControlStateNormal];
//返回到原来View
[self dismissViewControllerAnimated:YES completion:^{}];
}
|
(5)把咱们点击保存按钮回调的方法做以下修改,若是person为空,咱们会新建一个新的person.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
- (IBAction)tapSave:(id)sender
{
//若是person为空则新建,若是已经存在则更新
if
(self.person == nil)
{
self.person = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Person
class
]) inManagedObjectContext:self.managedObjectContext];
}
//赋值
self.person.name = self.nameTextField.text;
self.person.tel = self.telTextField.text;
self.person.firstN = [NSString stringWithFormat:@
"%c"
, pinyinFirstLetter([self.person.name characterAtIndex:0])-32];
//把button上的图片存入对象
UIImage *buttonImage = [self.imageButton imageView].image;
self.person.imageData = UIImagePNGRepresentation(buttonImage);
//保存
NSError *error;
if
(![self.managedObjectContext save:&error]) {
NSLog(@
"%@"
, [error localizedDescription]);
}
//保存成功后POP到表视图
[self.navigationController popToRootViewControllerAnimated:YES];
}
|
(6)由于是何更新页面公用的因此咱们要在viewDidLoad对TextField和Button的背景进行初始化,若是person中的imageData有值咱们有用传过来的图片,不然用默认的图片,添加数据初始化代码以下:
1
2
3
4
5
6
7
8
9
|
self.nameTextField.text = self.person.name;
self.telTextField.text = self.person.tel;
if
(self.person.imageData != nil)
{
UIImage *image = [UIImage imageWithData:self.person.imageData];
[self.imageButton setImage:image forState:UIControlStateNormal];
}
|
4.上面的代码就能够插入头像了,咱们须要在tableView中进行显示便可,在tableView中从person对象中获取相应的头像,而后显示便可,下面咱们要加上索引。
(1)在cell中显示头像的代码以下:
1
2
3
4
|
if
(person.imageData != nil) {
UIImage *image = [UIImage imageWithData:person.imageData];
cell.imageView.image = image;
}
|
(2)实现添加索引回调的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//给咱们的通信录加上索引,下面的方法返回的时一个数组
-(NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView
{
//经过fetchedResultsController来获取section数组
NSArray *sectionArray = [self.fetchedResultsController sections];
//新建可变数组来返回索引数组,大小为sectionArray中元素的多少
NSMutableArray *index = [NSMutableArray arrayWithCapacity:sectionArray.count];
//经过循环获取每一个section的header,存入addObject中
for
(
int
i = 0; i < sectionArray.count; i ++)
{
id <NSFetchedResultsSectionInfo> info = sectionArray[i];
[index addObject:[info name]];
}
//返回索引数组
return
index;
}
|
通过上面的步骤,咱们以前俩个页面能够共用,并且加上了头像和索引,运行效果以下:
上面的内容挺多的啦吧,别着急,咱们的这个通信录还没完呢,通信录中的查询功能是少不了的,由于当存的用户多了,为了方便用户查询咱们还须要添加一个控件。接下来是咱们Search Bar and Search 出场的时候了。UISearchDisplayController本身有一个TableView用于显示查询出来的结果,须要在通信录中添加一些代码咱们的Seach Bar就可使用了。
1.在storyboard中添加Search Bar and Search,而后把属性拖入咱们对应的TableViewController中便可,新添加属性以下:
//添加Search Display Controller属性 @property (strong, nonatomic) IBOutlet UISearchDisplayController *displayC;
2.编辑SearchBar内容改变后调用的方法,咱们会经过用户输入的内容进行一个模糊查询,把查询的内容添加到咱们以前的fetchResultController中
1 //当search中的文本变化时就执行下面的方法 2 - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText 3 { 4 //新建查询语句 5 NSFetchRequest * request = [[NSFetchRequest alloc]initWithEntityName:NSStringFromClass([Person class])]; 6 7 //排序规则 8 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstN" ascending:YES]; 9 [request setSortDescriptors:@[sortDescriptor]]; 10 11 //添加谓词 12 NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name contains %@",searchText]; 13 [request setPredicate:predicate]; 14 15 //把查询结果存入fetchedResultsController中 16 self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"firstN" cacheName:nil]; 17 18 NSError *error; 19 if (![self.fetchedResultsController performFetch:&error]) { 20 NSLog(@"%@", [error localizedDescription]); 21 } 22 }
3.由于UISearchDisplayController里的TableView和咱们以前的tableView用的是一个FetchedReaultsController,因此在UISearchDisplayController取消的时候要重载一下咱们以前的TableView,或去通信录中的FetchedResultsController, 代码以下:
//当在searchView中点击取消按钮时咱们从新刷新一下通信录 -(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { [self viewDidLoad]; }
4.由于经过search查询的结果集会显示在UISearchDisplayController本身的tableView中,因此加载cell时要进行相应的选择,search中的cell是咱们自定义的cell, 选择代码以下:
1 //根据不一样的tableView来设置不一样的cell模板 2 if ([tableView isEqual:self.tableView]) 3 { 4 5 cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; 6 7 } 8 else 9 { 10 cell = [tableView dequeueReusableCellWithIdentifier:@"SearchCell" forIndexPath:indexPath]; 11 12 }
5.在咱们的查询后的列表中,若是还想点击cell之后跳转到编辑页面,咱们该如何作呢? 添加下面的回调方法,用代码进行跳转,代码以下:
1 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 2 { 3 if ([tableView isEqual:self.displayC.searchResultsTableView]) 4 { 5 Person *person = [self.fetchedResultsController objectAtIndexPath:indexPath]; 6 UIStoryboard * s = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; 7 8 //获取要目标视图 9 UIViewController *destination = [s instantiateViewControllerWithIdentifier:@"EditViewController"]; 10 11 //键值编码传值 12 [destination setValue:person forKeyPath:@"person"]; 13 14 [self.navigationController pushViewController:destination animated:YES]; 15 } 16 }
通过上面的步骤,咱们的查询功能就写好了,下面是最终的运行结果:
经上面这么曲折的过程,咱们的通信录的基本功能就差很少了