本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。
上一节,介绍了实现XMPP所必须的服务器及数据库搭建工作,如果感兴趣的话,请查看上一篇博客:XMPP介绍一:服务器及数据库搭建。后端的准备工作介绍完毕,现在来介绍一下,客户端开发XMPP所必须具备的储备知识。XMPP架构在客户端的数据持久化操作是通过Core Data实现的,所以这一节的内容,我将为大家详细的说说Core Data的理论知识及基本使用。
一、Core Data基本介绍
Core Data 是苹果实现的一种数据持久化机制,可以方便的用来对SQLite进行CRUD操作。我们经常使用的第三方库FMDB也是对SQLite进行的数据库操作。Core Data的特点就是不用自己书写SQL语句,只需要手动配置对象模型,Core Data这个框架就会自动的帮助我们完成对象实体的映射工作,说简单一点,就是它会自动的帮助我们生成操作数据库的SQL语句。它可以将OC对象整体映射到数据库表中,也可以将数据库表中的数据映射成OC对象直接取出使用。Core Data的核心思想:ORM。 ORM框架的思想最早起源于Java的Hibernate持久化框架;后来.NET 也出了一套同样的机制,称之为Entity Framework;到了iOS5.0中,就称之为Core Data,其实都是同一个意思。为了说明ORM的中心思想,我绘制了一张分析图,如下:
1. 对象模型: 现在大部分的编程语言都支持面向对象思想,上图中左侧图形就表示实体。
2. 关系模型: 指的就是关系型数据库存储的结构,上图中右侧表结构。
3. Core Data的工作就是负责这两种模型之间的数据切换,方便的完成数据存取(避免了书写那些SQL语句)。
二、Xcode创建模型文件的过程
1. 选择模板
2. 添加实体(点击Add Entity)
3. 添加属性
4. 创建NSManagedObject的子类
默认情况下,利用Core Data取出的实体都是NSManagedObject类型的,能够利用键-值对来存取数据。但是一般情况下,实体在存取数据的基础上,有时还需要添加一些业务方法来完成一些其他任务,那么就必须创建NSManagedObject的子类
4.1 创建子类
4.2 选择模型文件
4.3 选择需要创建子类的实体
4.4 创建完毕后,就可以看到类的信息(包括分类)
4.5 类中的内容
Employee.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
NS_ASSUME_NONNULL_BEGIN
@interface Employee : NSManagedObject
// Insert code here to declare functionality of your managed object subclass
@end
NS_ASSUME_NONNULL_END
#import "Employee+CoreDataProperties.h"
Employee+CoreDataProperties.h
#import "Employee.h"
NS_ASSUME_NONNULL_BEGIN
@interface Employee (CoreDataProperties)
@property (nullable, nonatomic, retain) NSString *name;
@property (nullable, nonatomic, retain) NSNumber *age;
@property (nullable, nonatomic, retain) NSNumber *height;
@end
NS_ASSUME_NONNULL_END
三、Core Data中的核心对象
在完成上述操作后,模型文件就已经处理完毕了,接下来的工作就是创建SQLite数据库,及建立相应的连接关系,完成CRUD等操作,在介绍具体的操作之前,我们先来分析一下Core Data中的核心对象,如下图:
1. NSManagedObjectContext:Core Data的请求上下文,就是利用它来完成CRUD等操作。
2. NSPersistentStoreCoordinator:持久化存储调度器,可以利用它来指定并且创建用来持久化数据的SQLite数据库。
3. NSManagedObjectModel:Core Data操作数据的模型对象,可以将所有的模型文件关联起来(模型文件就是后缀名为xcdatamodeld的里面所有的实体文件)。
4. NSEntityDescription:描述实体中属性的详细信息。
有了以上的分析,我们就可以用代码来实现上面一系列的流程工作。
- (void)viewDidLoad {
[super viewDidLoad];
[self setupContext];
}
- (void)setupContext {
// 创建上下文对象
self.context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// 创建一个模型对象,nil参数表示会将所有模型文件 关联起来
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
// 持久化存储调度器
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 存储数据库路径
NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *sqlitePath = [document stringByAppendingPathComponent:@"company.sqlite"];
NSError *error;
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:sqlitePath] options:nil error:&error];
self.context.persistentStoreCoordinator = store;<span style="color:#00afca;">
}</span>
四、利用Core Data完成CRUD操作
1. Create(增加操作)
- (IBAction)addRecord:(id)sender {
Employee *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:self.context];
employee.name = @"kimmi";
employee.age = @23;
employee.height = @1.60;
NSError *error;
[self.context save:&error];
if (error) {
NSLog(@"保存失败");
} else {
NSLog(@"保存成功");
}
}
注:Core Data的增、删、改操作,最终都需要执行 NSManagedObjectContext实例对象的save方法。在调用save方法之前执行的操作全是在内存中执行的,而只有save操作才会将数据的变更同步到SQLite中!
2. Delete (删除操作)
- (IBAction)deleteRecord:(id)sender {
// 1. 先获取指定对象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@", @"jason"];
request.predicate = pre;
NSArray *items = [self.context executeFetchRequest:request error:nil];
// 2. 先删除内存中的数据
for (Employee *emp in items) {
[self.context deleteObject:emp];
}
// 3. 同步到数据库
[self.context save:nil];
}
3. Update (更新操作)
- (IBAction)updateRecord:(id)sender {
// 1. 先获取指定对象
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@", @"rose"];
request.predicate = pre;
NSArray *items = [self.context executeFetchRequest:request error:nil];
// 2. 修改内存中的数据
if (items.count > 0) {
Employee *emp = [items firstObject];
emp.age = @(24);
emp.height = @(1.83);
}
// 3. 同步到数据库
[self.context save:nil];
}
4. R etrieve (查询操作)
- (IBAction)searchRecords:(id)sender {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 1. 过滤条件(精确查找)
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@", @"jason"];
// request.predicate = pre;
// 2. 过滤条件(大小查找)
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"height > %@ and age > %@", @(1.0), @(20)];
// request.predicate = pre;
// 3. 分页查询
// request.fetchOffset = 4;
// request.fetchLimit = 2;
NSError *error;
// 查询结果
NSArray *items = [self.context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"查询失败");
} else {
for (Employee *employee in items) {
NSLog(@"name:%@, age:%@, height:%@", employee.name, employee.age, employee.height);
}
}
}
- (IBAction)fuzzySearch:(id)sender {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 1. 匹配开头
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name beginswith %@", @"ro"];
// request.predicate = pre;
// 2. 匹配结尾
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name endswith %@", @"mi"];
// request.predicate = pre;
// 3. contains
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name contains %@", @"m"];
// request.predicate = pre;
// 4. like
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name like %@", @"*m*"];
request.predicate = pre;
NSError *error;
// 查询结果
NSArray *items = [self.context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"查询失败");
} else {
for (Employee *employee in items) {
NSLog(@"name:%@, age:%@, height:%@", employee.name, employee.age, employee.height);
}
}
}
5. 处理多表关联的问题
5.1 我们在原来的模型上面,再建立一个Department实体
5.2 在Employee表中建立关联,即每一个员工隶属于某一个部门(depart这个Relationship)
5.3 建立完成这个关联关系后,我们再来看看Employee+CoreDataProperties.h这个文件,可以明显的看出,
多了一个depart对象属性。
#import "Employee.h"
NS_ASSUME_NONNULL_BEGIN
@interface Employee (CoreDataProperties)
@property (nullable, nonatomic, retain) NSString *name;
@property (nullable, nonatomic, retain) NSNumber *age;
@property (nullable, nonatomic, retain) NSNumber *height;
@property (nullable, nonatomic, retain) NSString *city;
@property (nullable, nonatomic, retain) Department *depart;
@end
NS_ASSUME_NONNULL_END
- (IBAction)queryRecords:(id)sender {
// 查询ios部门的员工
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
NSPredicate *pre = [NSPredicate predicateWithFormat:@"depart.name = %@", @"ios"];
request.predicate = pre;
NSError *error;
// 查询结果
NSArray *items = [self.context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"查询失败");
} else {
for (Employee *employee in items) {
NSLog(@"name:%@, age:%@, height:%@", employee.name, employee.age, employee.height);
}
}
}
注意到,NSPredicate中的过滤条件是 depart.name, 它就是直接操作了Employee对象中的depart对象的name属性。
这样,就不用自己书写数据库关联表的SQL语句了。