什么是CoreData?
CoreData是iOS5之后才出现的一个框架, 它提供了对象-关系映射(ORM)
的功能, 即能够将OC对象转换成数据, 保存在SQLite数据库文件中, 也能够将保存在数据库中的数据还原成OC对象.
这个过程中, 我们不需要编写任何的sql语句, 这个有点类似于著名的Hibernate持久框架, 不过功能肯定没有Hibernate强大.
CoreData是如何将数据直接插入数据库呢?
CoreData的使用步骤
一.为没有使用CoreData的工程添加CoreData
- 1.
Command+N
手工创建.xcdatamodeld文件, 这个过程相当于创建了一个数据库的模板(类型) - 2.在.xcdatamodeld文件中, 创建一个实例, 并且为其对象指明对象及类型
- 3.
Command+N
创建这个实例
的实例类
(CoreData->NSManagedObject subclass) - 4.在使用coreData控制器的位置, 进行初始化:
1
| #import <CoreData/CoreData.h>
|
1
| @property(nonatomic,strong)NSManagedObjectContext * context;
|
-ViewDidLoad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 从主Boundle中获取我们刚才创建的那个数据库模型 NSManagedObjectModel * model = [NSManagedObjectModel mergedModelFromBundles:nil]; // 持久化调度器, 相当于管理人员, 他负责解释/分析上下文的语法, 负责上下文与数据库的交互, 所以它的初始化, 需要指定一个数据库模型. NSPersistentStoreCoordinator * store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; // 数据库文件的位置 NSString * doc = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; // 数据库文件名 NSString * sqlite = [doc stringByAppendingPathComponent:@"company.sqlite"]; // 告诉管理人员, 需要管理的数据库的名字和路径 [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:sqlite] options:nil error:nil]; // 声明了一个上下文, 负责这个上下文的协调器就是上一步创建的store. NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init]; // 上下文关联一下数据库, 需要一个持久化对象, context.persistentStoreCoordinator = store; |
画一张图来解释一下上面的过程:
- 图中过程1表示, 从mainBoundle中, 将我们刚才手动创建的.xcdatamodeld文件,
反归档
到一个对象(model)中, 并且使用这个模型作为初始化条件(模板), 实例化一个协调器, 那么, 这个协调器就知道了自己所管理的数据库是什么样的(里面有什么东西). - 过程2表示, 协调器根据刚才的模板, 为自己添加一个数据库, 协调器会根据手中的模板, 将这个数据库“改造”成.xcdatamodeld文件描述的样子.
- 过程3表示, 我们创建一个上下文, 并且将该上下文对象托管给刚才的协调器.
- 最终, 上下文和数据库通过协调器建立起联系, 我们操作上下文, 达到操作数据库的目的.
CoreData实现实例的增删改查
向表中增加一个对象
IBAction响应事件:
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 | // 数据库准备好了, 开始做基本的操作. // !!!:添加一个员工 -(IBAction)addEmployee:(id)sender { /* 这个对象不能简单的使用 alloc+init 方法创建, 因为这样创建的对象, 和我们的上下文没有关系. 使用NSEntityDescription方法, 可以从当前上下文所指向的 协调器 对应的 数据库模型中, 拿出这个类. 在这个过程中, 我们使用到了_context, 进而将这个对象和我们的_context建立了联系.*/ // 创建一个员工对象 Employee * emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:_context]; emp.name = @"李聪"; emp.height = @180; emp.birthday = [NSDate date]; // 调用上下文的保存方法. NSError * error ; [_context save:&error]; if (error) { NSLog(@"%@",error); } } |
通过上面的示例我们可以看到:
1.整个操作过程我们没有任何使用sql语句的地方. 这就是封装的好处之一.
2.想要入库的对象, 必须通过NSEntityDescription返回对象类型.
如果用图表示的话, 应该是这样
从表中查询数据.
IBAction响应事件:
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 | // !!!:查员工 // 在查询数据时, 你需要创建一个请求的对象, 这个对象你可以把它理解成一份申请表, 在上面填写你需要的数据, 过滤条件, 排序规则等约束条件. 然后把这个申请表交给上下文去执行. -(IBAction)selectALlEmp:(id)sender { // 创建请求清单. (向清单上填写, 你需要查询那张表的数据?) NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 设置请求条件, 谓词匹配. (在清单上继续写, 需要什么过滤条件不?) NSPredicate * pre = [NSPredicate predicateWithFormat:@"name = %@", @"强哥"]; request.predicate = pre; // 设置排序 (继续清单上写, 查询好的数据, 用不用/按照哪个字段排序?) request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES]]; 上面的清单都填好了吧? 那么就将这个request清单, 交给_context上下文去执行吧! // 执行, 结果给你返回一个字典, 元素为这个表对应的实例类的对象. NSError * error = nil; NSArray * array = [_context executeFetchRequest:request error:&error]; if (error) { NSLog(@"%@",error); return; } NSLog(@"%@",array); } |
这个过程是不是比较繁琐?
试试Xcode自带的代码块:fetch, 上面的方法中, 输入这个单词试试!~
从表中修改数据.
IBAction响应事件:
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 | // !!!:修改员工信息 // 修改信息和删除信息, 都需要先通过上下文查找到需要操作的数据, 然后再对其修改或删除 -(IBAction)updateEmp:(id)sender { // 填写一个查询清单, 想要修改数据先得查询出来. NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 设置请求条件 NSPredicate * pre = [NSPredicate predicateWithFormat:@"name = %@", @"李聪"]; request.predicate = pre; // 执行, 返回结果 NSError * error = nil; NSArray * array = [_context executeFetchRequest:request error:&error]; if (error) { NSLog(@"%@",error); return; } 以上动作是为了先找出需要修改的数据, 这些数据存到了一个数组array中, // 注意, 这个数组是上下文中的数组, 因为, 它是由上下文查询后得到的. // 更改 for (Employee * emp in array) { if ([emp.name isEqualToString:@"李聪"]) { emp.height = @2.0; } } // 修改之后, 保存当前上下文. [_context save:nil]; } |
从表中删除数据.
IBAction响应事件:
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 | // !!!: 删除 -(IBAction)delEmp:(id)sender { // 填写一个查询清单, 想要删除数据先得查询出来. NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 设置请求条件 NSPredicate * pre = [NSPredicate predicateWithFormat:@"name = %@", @"李聪"]; request.predicate = pre; // 执行, 返回结果 NSError * error = nil; NSArray * array = [_context executeFetchRequest:request error:&error]; if (error) { NSLog(@"%@",error); return; } // 删除某人 for (Employee * emp in array) { if ([emp.name isEqualToString:@"李聪"]) { // 这里注意, 不是删除array中的数据, 是从上下文中将这个对象删除. [_context deleteObject:emp]; } } // 保存 [_context save:nil]; } |
CoreData的增删改查我们已经完成了, 接下来我们回到sqlite的世界中, 来看看用sql语句如何实增删改查.下面的例子是非常简单的例子, 但是却是数据库开发的基本语法元素.
简单DDL语法:
增: insert into TableName ([colum1, colum2, colum3]) values ([张三, 28, 男]);
删: delete from TableName where name = “张三”;
改: update TableName set name = “李四”;
查: select name from TableName where name = “李四”;
这些基本的语法加上表关联(jion), 能达到的效果就不一般了, 假设我们有两张表: tableA 和 tableB
1 2 3 4 5 6 7 8 9 10 | -- 您能猜出我用的是什么数据库语言吗? select A.name, A.grade, B.mathScore from tableA A left join tableB B on A.id = B.id where A.gender = '男' |
上面这段代码, 大概描述了 两张表关联使用, 从A表中取得学生的名字, 从B表中获得了学生的年级和数学成绩.
这样简单的代码您可能在大学的时候学习过, 但是请不要小觑它, 这种select格式能完成的功能远远超乎您的想象.
我这里没有使用场景, 无法为您演示一些复杂的示例.
那么对于CoreData, 我们不用直接接触sql语句, 这种表间的联合查询我们应该怎么办呢?
CoreData 的联合查询.
1.我们创建一个部门
的示例, 请注意 Employee 的 Releationships 部分.
这里, 实际上Department做为Employee的外键, 在Employee中有一个字段为depart. 如此设置之后,这两张表已经完成前面我们描述的表间关联
, 不用出现join关键字, 我们已经将两张表牢牢的绑在一起了.
在上代码之前, 我们还需要重新生成以下 Employee和Department的 实例类文件.
CoreData -> NSManagedObject subclass
没得选, 只有这一个.
全部勾选上, 为这两个实例产生两组实例类.
生成之后, 他们从boundle中看起来是这样子的.
下面, 我们向这两个实例中, 分别插入两条数据, 注意:如果你使用的是刚才的工程, 请先删除Doucment中数据库文件, 然后运行demo重新生成一个.
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 | -(IBAction)addDepartEmp:(id)sender { // 创建两个部门, ios 和 安卓 Department * depiOS = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:_context]; depiOS.departmentName = @"iOS部门"; depiOS.departmentID = @1; depiOS.createDate = [NSDate date]; Department * depAndroid = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:_context]; depAndroid.departmentName = @"安卓部门"; depAndroid.departmentID = @2; depAndroid.createDate = [NSDate date]; // 创建两个员工, 李聪->iOS部门, 小明->安卓部门 Employee * emp1 =[NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:_context]; emp1.name = @"李聪"; emp1.height = @180; emp1.birthday = [NSDate date]; emp1.depart = depiOS; Employee * emp2 =[NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:_context]; emp2.name = @"小明"; emp2.height = @175; emp1.depart = depAndroid; emp2.birthday = [NSDate date]; [_context save:nil]; } |
插入数据之后, 我们再写一个新的查询方法, 这次我只查询 Employee, 那么看看 这个Employee对象中, 是否包含部门的信息?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | -(IBAction)selectAllEmpDepart:(id)sender { // 找到强哥, 获取请求对象, 创建时, 你需要指定要查询那张表. NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"]; // 设置请求条件, 谓词匹配 NSPredicate * pre = [NSPredicate predicateWithFormat:@"name = %@", @"李聪"]; request.predicate = pre; // 执行, 返回结果 NSError * error = nil; NSArray * array = [_context executeFetchRequest:request error:&error]; if (error) { NSLog(@"%@",error); return; } // 注意, 这里我只查询了员工(Employee), 但是部门信息也顺带查出来了. for (Employee * emp in array) { NSLog(@"%@->%@", emp.name, emp.depart.departmentName); } } |
结果:
1
| 强哥->iOS部门
|
所以! CoreData可以通过创建表外键的方式, 将表关联起来. 但是作为Sql开发多年的老人来说, 这种方非常笨重.
二.新建带有CoreData的工程
- 创建工程时, 我们勾选下面的
Use Core Data
.
- 在主Boundel中, 最明显的就是我们创建了数据库模板(.xcdatamodeld文件)
- 打开Appdelegate.h文件, 多了如下属性和方法:
1 2 3 4 5 6
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory;
在appdelegate.m中, 多了如下的代码
千万别害怕, 静下心来好好看看, 这些方法和属性, 对比我们最开始ViewDidLoad里面的过程, 仔细读一遍, 看看它们再干什么呢? 提示: 懒加载.
最后, 我们来比对一下Sqlite和CoreData的优劣和不同的使用场景.
名称 | 优点 | 缺点 |
---|---|---|
sqlite | 灵活, 非常灵活 | 当项目中, 模型Model多起来后, 非常不方便, 需要大量代码重写Model的访问和改写等 |
CoreData | 面对大量模型时, 非常方便的创建了所有实例类 | 但是一旦这些模型之间的关系较为复杂时, 它的优雅便成了臃肿 |
最后总结一下, 这两种数据持久化方案都有各自的使用场景:
-
sqlite 适合数据模型(Model类)稀少, 不需要大量的Model类入库, 但是这些模型之间, 有这复杂的联系, 表间满足各种范式规范, 比如金融类APP, 其中数据需要在手机端完成运算和展示时, 使用sqlite 会非常的方便.
-
CoreData 适合使用大量Model 的APP, 这些Model之间联系不是那么紧密. 每个模型都是单独拿出来用的, 那么CoreData为你生成的模型访问方法会让你信心百倍!