一 、NSManagedObjectContext
1、我们要想操作Core Data,首先需要一个NSManagedObjectContext
2、那我们如何获得Context呢:创建一个UIManagedDocument
二、UIManagedDocument
1、UIManagedDocument是一系列用于管理存储的机制:
【将Core Data数据库放入某存储空间,相当于是管理core data 数据库的存储,所以我们只需要打开和存储】
2、那我们如何得到UIManagedDocument呢?如何在用户文档中创建一个UIDocument?
1 //(1文件管理器能够给我们一个用户文件目录的URL 2 NSFileManager *fileManager = [NSFileManager defaultManager]; 3 NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]; 4 5 //(2然后加上我们想要的文档名 6 NSString *documentName = @“MyDocument”; 7 NSURL *url = [documentsDirectory URLByAppendingPathComponent:documentName]; 8 //(3这个URL就是core data数据库存储的地方 9 UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
3、但是我们创建的这个文档还并不存在于我们的磁盘中,还需要存储到磁盘
1 //(1先判断是否已经存在于磁盘 2 BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];! 3 //(2.1如果已经存在于磁盘,则直接打开 4 [document openWithCompletionHandler:^(BOOL success) { /* block to execute when open */ }]; ! 5 //(2.2否则还要先存储到磁盘 6 [document saveToURL:url forSaveOperation:UIDocumentSaveForCreating competionHandler:^(BOOL success) { /* block to execute when create is done */ }];
e.g.下面我们看一个综合了上面的具体的例子,如何通过UIManagedDocument得到NSManagedObjectContext:
1 self.document = [[UIManagedDocument alloc] initWithFileURL:(URL *)url]; 2 if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) { 3 [document openWithCompletionHandler:^(BOOL success) { 4 if (success) [self documentIsReady]; 5 if (!success) NSLog(@“couldn’t open document at %@”, url); 6 }]; 7 } else { 8 [document saveToURL:url forSaveOperation:UIDocumentSaveForCreating 9 completionHandler:^(BOOL success) { 10 if (success) [self documentIsReady]; 11 if (!success) NSLog(@“couldn’t create document at %@”, url); 12 }]; 13 }
然后我们在 documentIsReady做一些操作从而获得context:
1 - (void)documentIsReady 2 { 3 /*对应的状态有: 4 UIDocumentStateClosed (还没有打开或者创建) 5 UIDocumentStateSavingError (completion handler保存没有成功) 6 UIDocumentStateEditingDisabled (重试) 7 UIDocumentStateInConflict(例如有其他人在使用更新,有冲突到等)*/ 8 9 if (self.document.documentState == UIDocumentStateNormal) { 10 //如果成功的话,我们就获得了我们需要的context了,然后操作core data 11 NSManagedObjectContext *context = self.document.managedObjectContext; 12 } 13 }
4、注意的点
【但要注意的点1:上面UIManagedDocument的打开、关闭或者创建(存储)都是异步执行的】
【需要注意的点2: UIManagedDocument是自动保存的,我们也可以调用上面的自己保存,对应的关闭也是自动关闭的】
【需要注意的点3: UIManagedDocument是多实例的,也就是说可以多控制器同时操作,但对应的同时只有一个可写】
5、广播站:
比如我们在一个文档中修改了数据CoreData,但是同时 另一个并没有能马上看到,这是因为它们所用的Context不同,要想能看到需要使用NSNotiFication广播站
三、NSNotiFication广播站
1、如何注册一个广播
1 - (void)viewDidAppear:(BOOL)animated 2 { 3 [super viewDidAppear:animated]; 4 [center addObserver:self 5 selector:@selector(contextChanged:) 6 name:NSManagedObjectContextDidSaveNotification 7 object:document.managedObjectContext]; // don’t pass nil here! 8 } 9 - (void)viewWillDisappear:(BOOL)animated 10 { 11 [center removeObserver:self 12 name:NSManagedObjectContextDidSaveNotification 13 object:document.managedObjectContext]; 14 [super viewWillDisappear:animated]; 15 }
2、广播得到消息之后能做什么?如何在contextChanged里操作?
(1 可以取回我的所有对象
1 - (void)contextChanged:(NSNotification *)notification 2 { 3 // notification.userInfo 返回给我们的是一个字典包含以下的key 4 NSInsertedObjectsKey //插入的对象数组 5 NSUpdatedObjectsKey // 有属性更改的对象数组 6 NSDeletedObjectsKey // 有删除的对象数组 7 }
(2 Merging changes:只要把notification传给它,它会自动帮我们把所有的变化合并到我们的context中
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification;
四、Core Data
上面的操作都完备,我们就可以对我们的数据库进行增删改的操作了
【这些操作是在内存中,不是在磁盘,但是别忘记了Document是自动保存的,所以最终还是会保存到磁盘的,只要文档保存了,Context就保存了】
1、插入
1 NSManagedObjectContext *context = aDocument.managedObjectContext; 2 //实体的名称:@“EntityBook”,返回NSManagedObject对象【数据库所有对象都是它或者它的子类】 3 NSManagedObject *book = [NSEntityDescription insertNewObjectForEntityForName:@“EntityBook” inManagedObjectContext:context]; 4 //设置属性 5 - (id)valueForKey:(NSString *)key; 6 - (void)setValue:(id)value forKey:(NSString *)key; 7 或者也可以用:valueForKeyPath:/setValue:forKeyPath
设置属性例如:
NSString *myThumbnail = book.thumbnailURL;
book.lastViewedDate = [NSDate date];
book.whoTook = ...; //这里的whoTook指的是另一张表的关联关系
book.whoTook.name = @“CS193p Instructor”;
2、删除
//(1要注意的是这也不是马上删除,而是需要在自动保存之后才会删除,但是引用到book的地方需要在这个操作之后设置为nil
[aDocument.managedObjectContext deleteObject:book];
//(2 这个方法通常放在类别中,用于删除操作之后的某些更新
- (void)prepareForDeletion{}
3、查询
(1 NSFetchRequest 提出请求从数据库请求对象
指定要取回的实体、指定取回的对象大小数量、NSSortDescriptors排序、NSPredicate谓词哪一些数据
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“EntityBook”];
request.fetchBatchSize = 20;
request.fetchLimit = 100;
request.sortDescriptors = @[sortDescriptor];
request.predicate = ...;
(2 NSSortDescriptor 排序
1 NSSortDescriptor *sortDescriptor = 2 [NSSortDescriptor sortDescriptorWithKey:@“title” //排序的键 3 ascending:YES //YES是按字母排序,NO是反字母排序 4 selector:@selector(localizedStandardCompare:)]; //在排序中的对比,这里的localizedStandardCompare 指代像Mac finder中的排序方式一般 5
(3 NSPredicate谓词
1 NSString *serverName = @“IOS-7”; 2 NSPredicate *predicate = 3 [NSPredicate predicateWithFormat:@“bookName contains %@”, serverName]; 4 5 @“uniqueId = %@”, [flickrInfo objectForKey:@“id”] 6 @“name contains[c] %@”, (NSString *) 7 @“viewed > %@”, (NSDate *) 8 @“whoTook.name = %@”, (NSString *) 9 @“any photos.title contains %@”, (NSString *) 10 @“(name = %@) OR (title = %@)” 11 @“photos.@count > 5” 12 @“photos.photo.title.length" 13 [propertyListResults valueForKeyPath:@“photos.photo.@avg.latitude”] 14 15 // 复合谓词 16 NSArray *array = @[predicate1, predicate2]; 17 NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:array];
更多的使用可以查询:
https://developer.apple.com/library/ios/documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html.
demo请求的例子:
1 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“Photographer”]; 2 NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:-24*60*60]; 3 request.predicate = [NSPredicate predicateWithFormat:@“any photos.uploadDate > %@”, yesterday]; 4 request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@“name” ascending:YES]]; 5 6 NSManagedObjectContext *context = aDocument.managedObjectContext; 7 NSError *error; 8 NSArray *photographers = [context executeFetchRequest:request error:&error];
五. 生成实体的类
1、方法
Editor->Create NSManagedObject Subclass => 选择Model => 选择要生成的数据表EntityBook
2、可以修改这些系统生成的类?=> 最好不要,我们可以采用类别的方法,也就是Categories
【Categories类别可以添加方法到一个类,而不用创建它额的子类,你甚至不需要有该类的源代码】
六、Categories类别
【不能使用实例变量或者任何存储数据】
1、声明
1 @interface EntityBook (AddOn) 2 - (NSString *)note; 3 @property (readonly) BOOL isOld; 4 @end
2、实现
1 @implementation EntityBook (AddOn) 2 - (NSString *)note //要注意:note不是数据库表的属性,但是这里的bookName uploadDate是属性 3 { 4 NSString *bookNote = [NSString stringWithFormat:@"%@:lalalallal", self.bookName]; 5 return bookNote; 6 } 7 - (BOOL)isOld // 8 { 9 return [self.uploadDate timeIntervalSinceNow] > -24*60*60; 10 } 11 @end
3、大多数情况下的实现
1 @implementation EntityBook (Create) 2 3 + (EntityBook *)bookWithData:(NSDictionary *)Data inManagedObjectContext:(NSManagedObjectContext *)context 4 { 5 EntityBook *book = ...; // 查看具体某一本书是否存在 6 if (!book) 7 { 8 book = [NSEntityDescription insertNewObjectForEntityForName:@“EntityBook” inManagedObjectContext:context]; 9 } 10 return book; 11 } 12 @end
七、线程安全
1、NSManagedObjectContext并不线程安全,任何使用在下面的BLock里面去执行 它会对Context在安全队列中执行
[context performBlock:^{ or performBlockAndWait:
//使用对Context做的事情,例如插入对象、查询等等
}];
八、NSFetchedResultsController
[使得Core Data和UITableViewController能够相辅相成。]
1、例如:
- (NSUInteger)numberOfSectionsInTableView:(UITableView *)sender
{
return [[self.fetchedResultsController sections] count];
}
- (NSUInteger)tableView:(UITableView *)sender numberOfRowsInSection:(NSUInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = ...;
// 或者 Book *book = (Book *) ...
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
return cell;
}
2、那么如何构建一个NSFetchedResultsController呢?
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“EntityBook”];
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@“title” ...]];
//关联另一张表的查询,比如说作者表中的作者名称,作为我们book表的WhoTook
request.predicate = [NSPredicate predicateWithFormat:@“whoTook.name = %@”, authorName];
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:(NSFetchRequest *)request
managedObjectContext:(NSManagedObjectContext *)context
sectionNameKeyPath:(NSString *)keyThatSaysWhichSectionEachManagedObjectIsIn
cacheName:@“MyPhotoCache”// 指定为nil的话,就不会执行缓存处理[永久性在磁盘中]:所以只针对总有相同FetChRequest的TableView
];
3、让NSFetchwsResultController和TableView关联起来有两种方式,一种是利用它实现所有UITableViewDataSource的东西
第二种就是设置委托:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
// 更新TableView[可以利用CoreDataTableViewController]
}
4、CoreDataTableViewController