Core Data
就基础而言, 它是一种创建对象的方式, 创建你要处理的 object-c 对象, 并映射到 SQL 或者 XML 数据库.
它是面向对象领域同数据库领域之间的桥梁, Core Data 主要连接的就是 SQL 的后台.
Core Data 是如何工作的呢?
1.首先用 XCode 中的工具创建一个可视的映射.这就是在对象和数据库之间建立映射的方式.
有了这个映射之后,你就可以做很多事情, 但我们将聚集于创建实体,属性,和关系.
创建了映射后, 如何访问代码中的这些东西呢?
我们需要: NSManagedObjectContext
如何得到 NSManagedObjectContext?
有两种方法:
- 用 NSManagedObjectContext 的 alloc init 方法(课程中未讲)
- 调用用 UIManagedDocument 类
UIManagedDocument 继承于 UIDocument是一系列用于管理储存的机制.
你可以这样考虑 UIManagedDoument, 它为你容纳你的 Core Data 数据库, 你所做的只是打开或创建一个 UIManagedDocument, 然后抓取它的 ManagedObjectContext, 然后用它来做数据库.
创建本地目录
//创建共享文件管理器
NSFileManager *fileManager = [NSFileManager defaultManager];
//得到用户文件目录的 URL
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
//在url 后面再添加一个目录文件,只是生成 url 并没有真创建好,参数为目录名
NSURL *url = [documentsDirectory URLByAppendingPathComponent:@"MyDocument"];
1.创建 UIManagedDocument(文档) 对象(只是创建在内存,但还并没有写在磁盘上)
//url: 是将 Core Data 数据存储的地方
UIManagedDocument *document = [UIManagedDocument alloc] initWithFileURL:url];
2.判断磁盘上是否存在
//判断 url 路径是否在本地磁盘上
BOOL fileExists = [NSFileManager defaultManager] fileExistsAtPath:[url path]];
if(fileExists){
//如果存在,执行 block
[document openWithCompletionHandler:^(BOOL success){
/*里面为 block 部分*/
if(success){
//如果UIManagedDocument(文档)的状态是正常的
if(document.documentState = = UIDocumentStateNormal){
//取得 document 的 managedObjectContext
NSManagedObjectContext *context = document.managedObjectContext;
}
}
}];
}else{
//如果不存在,就创建文档到磁盘上
[document saveToURL:url forSavePoeration:UIDocumentSaveForCreating competionHandler:^(BOOL success){
/*里面为 block 部分*/
}];
}
中间临时插入:
相同文档上 UIManagedDocument 的多实例
能不能我对文件 URL 进行 alloc init, 来创建一个 UIManagedDocument, 然后其他人对相同文件 URL 进行 alloc init, 这样完全可以, 两者有自身的 ManagedObjectContext ,但 context 都着眼于相同的数据库, 它们都发生变化时, 会发生保存.
但这也有可能出问题, 会容易同时操作时产生冲突. 所以通常多实例, 只有一个能写其它那些只能读.
如何让一个修改了数据库, 另一个也可以自动看到相应的变化? 广播站(NSNotification),进行监听,
现在我们有了NSManagedObjectContext,来自于我们的文档(UIManagedDocument *document)
增加(插入):
假设我要添加一张新照片, 或者是一个新的拍照人到数据库中.
NSManagedObjectContext *context = document.managedObjectContext;
//在 context 上创建了一个 "Photo" 对象,@"Photo" 是实体的名称
NSManagedObject *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];
修改:
现在有了对象了, 要如何设定属性呢(如:标题,whoTook 等), 这要用到 KeyValueCoding 协议, 并且 NSManagedObject 实现了.
用这两个方法来设置属性
- (id)valueForKdy:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
这些修改只会保存在内存中, 直到你调用 NSManagedObjectContext 的保存方法才会保存到磁盘中.
但这种方法存在一定的问题,比如设置属性的方法都为 id 类型, 很容易出问题. 所以我们创建实体的子类
//注意: 是 Photo 类
Photo *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];
photo.title = [flickrData objectForKey:FLICKR_PHOTO_TITLE];
photo.lastViewedDate = [DSDate date];
photo.whoTook.name = @".....";
删除:
//调用context 的 deleteObject 方法
[context deleteObject:photo];
//删除对象之后最好,把它设置为 nil
photo = nil;
//删除时 Core Data 会发送下方方法, 执行完后才会删除
- (void)propareForDeletion{
}
查询:
通过 NSFetchRequest(取回请求) 对象来进行查询.你提出请求从数据库中取回一些对象.
查询的步奏:
1.首先你要设置请求(NSFetchRequest)
A.指定要取回的实体(Entity)类型
B.指定要接收多少个对象
C.如何排序,用 NSSortDescriptor(排序描述器)
D.想要哪些实体.用 Predicate(谓词)
2.调用 NSManagedObjectContext 的方法,执行请求
创建 NSSortDescriptor(排序描述器)
//selector 用于在排序过程中对比照片,结果将会依标题排序(其它创建方法看NSSortDescriptor类)
//compare:除了 NSData 对数据库中所有的东西都会做出响应, 只不过 NSString 里localizedStandardCompare: 方法更好
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(localizedStandardCompare:)];
创建 Predicate(谓词)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"thumbnailURL contains %@",@"flickr-5"];
进行查询(设置请求)
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.fetchBatchSize = 20;
request.fetchLimit = 100;
request.sortDescriptors = @[sortDescriptor];
request.predicate = predicate;
执行请求
使用 NSManagedObjectContext 中的executeFetchRequest 方法
NSArray *photographers = [context executeFetchRequest:request error:NULL];
关于 Core Data 的线程安全
NSManagedObjectContext 线程并不安全,你不能简单在多线程中使用它, 不过你可以通过安全访问它们
任何你希望对 context 做的事情,都可以在 performBlock 中执行
//调用 context 的 performBlock:方法
[context performBlock:^{
}]
Core Data 与 TableView 的共同协作
有两种方法可以让 FetchedResultsController(取回结果控制器) 同表格关联起来.
一是使用FetchedResultsController实现所有的UITableViewDataSource的东西.
二是设置FetchedResultsController的委托, 使用委托方法来监视.
使用 NSFetchedResultsController, 它的作用是将一个 NSFetchRequest, 和 UITableView 关联起来, Fetch取回的任何东西都总会显示在 TableView 中.
工作方面 FetchedResultsController 有两部分,
一是它回答了 UITableViewDataSource 中的所有问题
二是可以告诉你在给定行中显示的是什么实体
创建一个FetchedResultsController
//cache: 缓存,它会缓存取回的结果. 如果其值是 nil ,就会永久缓存在磁盘上(不是内存)
//sectionNameKeyPath:你指定要取回的对象中, 哪个属性是 section 的名称, 然后它会将表格分成 section
//如果你要使用 sectionNameKeyPaht 尝试把 section 放到 tableView 中, 排序描述器就需要匹配 section 键, 换言之, 表中的行, 你取回的照片, 必须和 section header 具有相同顺序
//sectionNameKeyPath控制返回的sectiond
NSFetchedResultsController *frc = [[FetchedResultsController alloc] initWithFetchRequest:(NSFetchRequest *)fetchRequest managedObjectContext:(NSManagedObjectContext *)context sectionNameKeyPath:(NSString *)sectionNameKeyPath cacheName:(NSString *)name];
FetchedResultsController 还有一个委托, 使用这个委托可以监视 Core Data 中发生的事情, 当有变化时 FetchRequest 会受影响, 从而让你的表格变化.
//didChangeObject:监听的广播站
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
有两种方法可以让 FetchedResultsController同表格关联起来