iOS应用开发视频教程笔记(十四)Core Data Demo
来源:互联网 发布:全职高手网络剧演员表 编辑:程序博客网 时间:2024/06/05 03:26
这节课的主要内容是Core Data的线程安全、Core DataTable View,以及大Demo。
Core Data Thread Safety
NSManagedObjectContext不是线程安全的,只能在创建NSManagedObjectContext的那个线程里访问它。一个数据库有多个UIManagedDocument和context,它们可以在不同的线程里创建,只要能管理好它们之间的关系就没问题。
线程安全的意思是,程序可能会崩溃,如果多路访问同一个NSManagedObjectContext,或在非创建实例的线程里访问实例,app就会崩溃。对此要怎么做呢?NSManagedObjectContext有个方法叫performBlock可以解决这个问题:
[context performBlock:^{ //or performBlockAndWait: // do stuff with context}];
它会自动确保block里的东西都在正确的context线程里执行,但这不一定就意味着使用了多线程。事实上,如果在主线程下创建的context,那么这个block会回到主线程来,而不是在其他线程里运行,这个performBlock只是确保block运行在正确的线程里。
NSManagedObjectContext,包括所有使用SQL的Core Data,都有一个parentContext,这就像是另一个NSManagedObjectContext,在真正写入数据库之前要写入到这里。可以获取到parentContext,可以让parentContext调用performBlock来做一些事,这总是在另一个线程里进行。parentContext和创建的NSManagedObjectContext不在一个线程里运行,可以通过performBlock在那个线程里执行你要做的事。记住,如果改变了parentContext,必须保存,然后重新获取child context。如果你想在非主线程载入很多内容,那么就全部放入数据库,然后在主线程去获取,这个效率非常快。
Core Data and Table View
ios里有一个非常重要的类NSFetchedResultsController,它不是一个viewController,而是控制fetchedResults与tableView通信方式的controller,所以这个NSFetchedResultsController是一个NSObject。
它的作用就是用来连接NSFetchedResultsController和UITableViewController的,而且连接的方法很强大。首先,它可以回答所有UITableView DataSource、protocol的问题,唯一不能回答的是cellForRowAtIndexPath。这是个NSFetchedResultsController的例子:
- (NSUInteger)numberOfSectionsInTableView:(UITableView *)sender{ return [[self.fetchedResultsController sections] count];}- (NSUInteger)tableView:(UITableView *)sender numberOfRowsInSection:(NSUInteger)section{ return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];}
tableView有个property是NSFetchedResultsController,就是这个self.fetchedResultsController。
cellForRowAtIndexPath也是很容易实现的,因为NSFetchedResultsController有objectAtIndexPath这个方法,你给出一个index,它会返回给你该row所显示的NSManagedObject:
- (NSManagedObject *)objectAtIndexPath:(NSIndexPath *)indexPath;
如果要实现cellForRowAtIndexPath,可以像下面这样做:
- (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell = ...; NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath]; // load up the cell based on the properties of the managedObject // of course, if you had a custom subclass, you’d be using dot notation to get them return cell;}
有了managedObject,可以用valueForKey或者子类它,用.号来获取它的property。在一个Core Data驱动的tableView里,每一行都是通过数据库里的对象来表示的,一对一的关系。
如何创建这个NSFetchedResultsController呢?这是个如何alloc/init的例子:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“Photo”]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@“title” ...]; request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; request.predicate = [NSPredicate predicateWithFormat:@“whoTook.name = %@”, photogName];NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:(NSFetchRequest *)request managedObjectContext:(NSManagedObjectContext *)context sectionNameKeyPath:(NSString *)keyThatSaysWhichSectionEachManagedObjectIsIn cacheName:@“MyPhotoCache”; // careful!
如果用这个fetch request来创建NSFetchedResultsController,那么table的每一行的照片的photographer名字都会对应这个字符串。cacheName就是缓存名,缓存速度很快,但有一点要小心:如果改变了fetch request的参数,比如改了predicate或者sortDescriptors,缓存会被破坏。如果真的要通过NSFetchedResultsController改fetch request,那么得删除缓存。NSFetchedResultsController里有个工厂方法可以删除缓存,或者可以就设为null,这样就不会有缓存,对于小型数据库,可以不用缓存。对于一个非常大型的数据库,如果要改request,得先删除缓存。另一个参数sectionNameKeyPath表示每个managedObject所在的section,这就是objects的一个attribute,这个例子里就是photo的一个attribute,如果问这个attribute关于photo的事,它会告诉我这个section的title,所以这里就是section的title。
如果context改变了,NSFetchedResultsController会自动更新table,只要把两个东西连接起来就行。唯一的前提是,这些改变都要发生在创建NSFetchedResultsController的那个context里。原理是,NSFetchedResultsController有个delegate,它会发很多消息,例如,添加了对象或者对象的一个属性改了等等,然后tableView就会去做相应的调整,事实上还是要在tableView里写些代码的。
Demo
这个demo会从Flickr上得到几百张照片,但table里显示的不是照片而是摄影师,点击摄影师就会显示他拍的所有照片的一个列表,点击photo才会显示图片。我们要实现从Flickr获取数据并存到Core Data数据库,然后把CoreDataTableView和fetch request连接起来。
在xcode新建project,从Single View Application开始,就叫它Photomania,在storyboard中从library拖出两个tableViewController,并将其中一个导入到导航控制器中,然后将开始小箭头移到导航控制器前面。新建两个UITableViewController的子类,分别为PhotographersTableViewController和PhotosByPhotographerTableViewController,到storyboard中将两个tableViewController的类分别设为这两个。
Photo+Flickr.h文件代码:
#import "Photo.h"@interface Photo (Flickr)+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo inManagedObjectContext:(NSManagedObjectContext *)context;@end
Photo+Flickr.m文件代码:
#import "Photo+Flickr.h"#import "FlickrFetcher.h"#import "Photographer+Create.h"@implementation Photo (Flickr)// 9. Query the database to see if this Flickr dictionary's unique id is already there// 10. If error, handle it, else if not in database insert it, else just return the photo we found// 11. Create a category to Photographer to add a factory method and use it to set whoTook// (then back to PhotographersTableViewController)+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo inManagedObjectContext:(NSManagedObjectContext *)context{ Photo *photo = nil; NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"]; request.predicate = [NSPredicate predicateWithFormat:@"unique = %@", [flickrInfo objectForKey:FLICKR_PHOTO_ID]]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES]; request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; NSError *error = nil; NSArray *matches = [context executeFetchRequest:request error:&error]; if (!matches || ([matches count] > 1)) { // handle error } else if ([matches count] == 0) { photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context]; photo.unique = [flickrInfo objectForKey:FLICKR_PHOTO_ID]; photo.title = [flickrInfo objectForKey:FLICKR_PHOTO_TITLE]; photo.subtitle = [flickrInfo valueForKeyPath:FLICKR_PHOTO_DESCRIPTION]; photo.imageURL = [[FlickrFetcher urlForPhoto:flickrInfo format:FlickrPhotoFormatLarge] absoluteString]; photo.whoTook = [Photographer photographerWithName:[flickrInfo objectForKey:FLICKR_PHOTO_OWNER] inManagedObjectContext:context]; } else { photo = [matches lastObject]; } return photo;}@end
Photographer+Create.h文件代码:
#import "Photographer.h"@interface Photographer (Create)+ (Photographer *)photographerWithName:(NSString *)name inManagedObjectContext:(NSManagedObjectContext *)context;@end
Photographer+Create.m文件代码:
#import "Photographer+Create.h"@implementation Photographer (Create)+ (Photographer *)photographerWithName:(NSString *)name inManagedObjectContext:(NSManagedObjectContext *)context{ Photographer *photographer = nil; NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photographer"]; request.predicate = [NSPredicate predicateWithFormat:@"name = %@", name]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]; request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; NSError *error = nil; NSArray *photographers = [context executeFetchRequest:request error:&error]; if (!photographers || ([photographers count] > 1)) { // handle error } else if (![photographers count]) { photographer = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:context]; photographer.name = name; } else { photographer = [photographers lastObject]; } return photographer;}@end
PhotographersTableViewController.h文件代码:
#import <UIKit/UIKit.h>#import "CoreDataTableViewController.h"// inherits from CoreDataTableViewController to get an NSFetchedResultsController @property// and to get all the copy/pasted code for the NSFetchedResultsController delegate from the documentation@interface PhotographersTableViewController : CoreDataTableViewController@property (nonatomic, strong) UIManagedDocument *photoDatabase; // Model is a Core Data database of photos@end
PhotographersTableViewController.m文件代码:
#import "PhotographersTableViewController.h"#import "FlickrFetcher.h"#import "Photographer.h"#import "Photo+Flickr.h"@implementation PhotographersTableViewController@synthesize photoDatabase = _photoDatabase;// 4. Stub this out (we didn't implement it at first)// 13. Create an NSFetchRequest to get all Photographers and hook it up to our table via an NSFetchedResultsController// (we inherited the code to integrate with NSFRC from CoreDataTableViewController)- (void)setupFetchedResultsController // attaches an NSFetchRequest to this UITableViewController{ NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photographer"]; request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]]; // no predicate because we want ALL the Photographers self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photoDatabase.managedObjectContext sectionNameKeyPath:nil cacheName:nil];}// 5. Create a Q to fetch Flickr photo information to seed the database// 6. Take a timeout from this and go create the database model (Photomania.xcdatamodeld)// 7. Create custom subclasses for Photo and Photographer// 8. Create a category on Photo (Photo+Flickr) to add a "factory" method to create a Photo// (go to Photo+Flickr for next step)// 12. Use the Photo+Flickr category method to add Photos to the database (table will auto update due to NSFRC)- (void)fetchFlickrDataIntoDocument:(UIManagedDocument *)document{ dispatch_queue_t fetchQ = dispatch_queue_create("Flickr fetcher", NULL); dispatch_async(fetchQ, ^{ NSArray *photos = [FlickrFetcher recentGeoreferencedPhotos]; [document.managedObjectContext performBlock:^{ // perform in the NSMOC's safe thread (main thread) for (NSDictionary *flickrInfo in photos) { [Photo photoWithFlickrInfo:flickrInfo inManagedObjectContext:document.managedObjectContext]; // table will automatically update due to NSFetchedResultsController's observing of the NSMOC } // should probably saveToURL:forSaveOperation:(UIDocumentSaveForOverwriting)completionHandler: here! // we could decide to rely on UIManagedDocument's autosaving, but explicit saving would be better // because if we quit the app before autosave happens, then it'll come up blank next time we run // this is what it would look like (ADDED AFTER LECTURE) ... [document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL]; // note that we don't do anything in the completion handler this time }]; }); dispatch_release(fetchQ);}// 3. Open or create the document here and call setupFetchedResultsController- (void)useDocument{ if (![[NSFileManager defaultManager] fileExistsAtPath:[self.photoDatabase.fileURL path]]) { // does not exist on disk, so create it [self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) { [self setupFetchedResultsController]; [self fetchFlickrDataIntoDocument:self.photoDatabase]; }]; } else if (self.photoDatabase.documentState == UIDocumentStateClosed) { // exists on disk, but we need to open it [self.photoDatabase openWithCompletionHandler:^(BOOL success) { [self setupFetchedResultsController]; }]; } else if (self.photoDatabase.documentState == UIDocumentStateNormal) { // already open and ready to use [self setupFetchedResultsController]; }}// 2. Make the photoDatabase's setter start using it- (void)setPhotoDatabase:(UIManagedDocument *)photoDatabase{ if (_photoDatabase != photoDatabase) { _photoDatabase = photoDatabase; [self useDocument]; }}// 0. Create full storyboard and drag in CDTVC.[mh], FlickrFetcher.[mh] and ImageViewController.[mh]// (0.5 would probably be "add a UIManagedDocument, photoDatabase, as this Controller's Model)// 1. Add code to viewWillAppear: to create a default document (for demo purposes)- (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; if (!self.photoDatabase) { // for demo purposes, we'll create a default database if none is set NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; url = [url URLByAppendingPathComponent:@"Default Photo Database"]; // url is now "<Documents Directory>/Default Photo Database" self.photoDatabase = [[UIManagedDocument alloc] initWithFileURL:url]; // setter will create this for us on disk }}// 14. Load up our cell using the NSManagedObject retrieved using NSFRC's objectAtIndexPath:// (go to PhotosByPhotographerViewController.h (header file) for next step)- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *CellIdentifier = @"Photographer Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // ask NSFetchedResultsController for the NSMO at the row in question Photographer *photographer = [self.fetchedResultsController objectAtIndexPath:indexPath]; // Then configure the cell using it ... cell.textLabel.text = photographer.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d photos", [photographer.photos count]]; return cell;}// 19. Support segueing from this table to any view controller that has a photographer @property.- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; Photographer *photographer = [self.fetchedResultsController objectAtIndexPath:indexPath]; // be somewhat generic here (slightly advanced usage) // we'll segue to ANY view controller that has a photographer @property if ([segue.destinationViewController respondsToSelector:@selector(setPhotographer:)]) { // use performSelector:withObject: to send without compiler checking // (which is acceptable here because we used introspection to be sure this is okay) [segue.destinationViewController performSelector:@selector(setPhotographer:) withObject:photographer]; }}@end
PhotosByPhotographerTableViewController.h文件代码:
#import <UIKit/UIKit.h>#import "Photographer.h"#import "CoreDataTableViewController.h"// inherits from CoreDataTableViewController to get an NSFetchedResultsController @property// and to get all the copy/pasted code for the NSFetchedResultsController delegate from the documentation@interface PhotosByPhotographerTableViewController : CoreDataTableViewController// 15. Added public Model (the photographer whose photos we want to show)@property (nonatomic, strong) Photographer *photographer;@end
PhotosByPhotographerTableViewController.m文件代码:
#import "PhotosByPhotographerTableViewController.h"#import "Photo.h"#import "ImageViewController.h"@implementation PhotosByPhotographerTableViewController@synthesize photographer = _photographer;// 17. Create a fetch request that looks for Photographers with the given name and hook it up through NSFRC// (we inherited the code to integrate with NSFRC from CoreDataTableViewController)- (void)setupFetchedResultsController // attaches an NSFetchRequest to this UITableViewController{ NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"]; request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]]; request.predicate = [NSPredicate predicateWithFormat:@"whoTook.name = %@", self.photographer.name]; self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photographer.managedObjectContext sectionNameKeyPath:nil cacheName:nil];}// 16. Update our title and set up our NSFRC when our Model is set- (void)setPhotographer:(Photographer *)photographer{ _photographer = photographer; self.title = photographer.name; [self setupFetchedResultsController];}// 18. Load up our cell using the NSManagedObject retrieved using NSFRC's objectAtIndexPath:// (back to PhotographersTableViewController.m for next step, segueing)- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *CellIdentifier = @"Photo Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // Configure the cell... Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath]; // ask NSFRC for the NSMO at the row in question cell.textLabel.text = photo.title; cell.detailTextLabel.text = photo.subtitle; return cell;}// 20. Add segue to show the photo (ADDED AFTER LECTURE)- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath]; // ask NSFRC for the NSMO at the row in question if ([segue.identifier isEqualToString:@"Show Photo"]) { [segue.destinationViewController setImageURL:[NSURL URLWithString:photo.imageURL]]; [segue.destinationViewController setTitle:photo.title]; }}@end
- iOS应用开发视频教程笔记(十四)Core Data Demo
- iOS应用开发视频教程笔记(十三)Core Data
- iOS应用开发视频教程笔记(十三)Core Data
- iOS应用开发视频教程笔记(十六)Action Sheets, Image Picker, Core Motion
- iOS开发之Core Data Demo (一)
- 2013斯坦福大学iOS应用开发学习笔记 13 Core Data
- iOS开发中,Core Data的使用笔记(一)
- 【IOS学习】Core Data 框架学习笔记,以及demo
- iOS应用开发视频教程笔记(十二)Persistence
- iOS应用开发视频教程笔记(九)Table Views
- iOS应用开发视频教程笔记iPad Apps
- iOS 7应用开发公开课笔记 L12:Documents and Core Data
- iOS 7应用开发公开课笔记 L13:Core Data and Table View
- iOS应用开发之Core Data数据持久化存储笔记
- IOS开发(96)之Core Data
- ios开发学习笔记--数据持久化之Core Data
- IOS开发学习笔记(十九)——Core Data使用(上篇)
- IOS开发学习笔记(二十)——Core Data使用(中篇)
- iOS: UIScrollViewDelegate Protocol的方法scrollViewDidEndScrollingAnimation:
- 我会努力提升自己
- 在eclipse下远程调试hadoop2.0
- Ones
- C语言二叉树的基本操作
- iOS应用开发视频教程笔记(十四)Core Data Demo
- WinForm webbrowser控件的使用
- iOS: UIScrollViewDelegate Protocol的方法scrollViewDidEndZooming:withView:atScale:
- 南阳理工ACM 106 背包问题
- POJ1679 The Unique MST
- 制作源码包
- Linux磁盘分区
- iOS: UIScrollViewDelegate Protocol的方法scrollViewDidScroll:
- iOS: UIScrollViewDelegate Protocol的方法scrollViewDidScrollToTop: