Cocoa notes (18) Core Data
星期四, 五月 26th, 2011这是一个比较重要的章节. Core Data是一个数据框架,主要目的是自动化管理数据,脱离实际的数据引擎接口,用统一的方式操作数据,比较类似于java中的hibernate?
* 要使用core data,需要#import
准备环境(初始化model, coordinator, context)
NSManagedObjectModel 用来描述应用中的数据模型(entities).
创建一个NSManagedObjectModel
1 2 | // 将合并所有在给出bundles参数内的model,如果mergedModelFromBundles传入nil则自动读取main bundle的 NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil]; |
NSPersistentStoreCoordinator 是永久存储介质(关系数据库)和managed object context中间的联系层,context必须有coordinator设置,否则就是不能工作的context。
1 2 3 4 | // 初始化 NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel]; // 设置数据库类型,数据库url等 [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error] |
NSManagedObjectContext 用于管理managed object
1 2 | self.context = [[[NSManagedObjectContext alloc] init] autorelease]; [self.context setPersistentStoreCoordinator:persistentStoreCoordinator]; |
以上过程合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | NSError *error; NSURL *url = [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingString:@"/Documents/cdintro_00.sqlite"]]; // Init the model, coordinator, context NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil]; NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel]; if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) NSLog(@"Error: %@", [error localizedDescription]); else { self.context = [[[NSManagedObjectContext alloc] init] autorelease]; [self.context setPersistentStoreCoordinator:persistentStoreCoordinator]; } [persistentStoreCoordinator release]; |
保存数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Insert objects for department and two people, setting their properties Department *department = (Department *)[NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:self.context]; department.groupName = @"Office of Personnel Management"; Person *person1 = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.context]; person1.name = @"John Smith"; person1.birthday = [self dateFromString:@"12-1-1901"]; person1.department = department; Person *person2 = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.context]; person2.name = @"Jane Doe"; person2.birthday = [self dateFromString:@"4-13-1922"]; person2.department = department; department.manager = person1; department.members = [NSSet setWithObjects:person1, person2, nil]; // Save the data NSError *error; if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]); |
NSEntityDescription 用来描述core data中的一个entity(对于数据库来说是一个表,对于代码来说是一个类)
NSManagedObject创建的另外一个方法
– initWithEntity:insertIntoManagedObjectContext:
会触发awakeFromInsert ,类似的方法同样还有awakeFromFetch
1 2 3 4 5 |
查询数据
NSFetchRequest用于描述一个查询中的参数(条件,排序,结果数等等)。
NSSortDescriptor 用于描述查询的排序方式,并可以提供自定义的排序比较方法。
NSFetchedResultsController用于管理查询返回结果并提供给UITableView。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | - (void) fetchObjects { // 创建Request NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:[NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context]]; // 添加一个排序描述 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:nil]; NSArray *descriptors = [NSArray arrayWithObject:sortDescriptor]; [fetchRequest setSortDescriptors:descriptors]; [sortDescriptor release]; // 初始化results controller,cache叫Root NSError *error; self.results = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:nil cacheName:@"Root"]; self.results.delegate = self; // 查询 if (![[self results] performFetch:&error]) NSLog(@"Error: %@", [error localizedDescription]); [self.results release]; [fetchRequest release]; } |
创建fetch request 模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | NSManagedObjectModel *model = <#Get a model#>; NSFetchRequest *requestTemplate = [[NSFetchRequest alloc] init]; NSEntityDescription *publicationEntity = [[model entitiesByName] objectForKey:@"Publication"]; [requestTemplate setEntity:publicationEntity]; NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat: @"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \ (mainAuthor.lastName like[cd] $LAST_NAME) AND \ (publicationDate > $DATE)"]; [requestTemplate setPredicate:predicateTemplate]; [model setFetchRequestTemplate:requestTemplate forName:@"PublicationsForAuthorSinceDate"]; [requestTemplate release]; |
获取模板并查询
1 2 3 4 5 6 7 8 9 10 | NSManagedObjectModel *model = <#Get a model#>; NSError *error = nil; NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys: @"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME", [NSDate dateWithTimeIntervalSinceNow:-31356000], @"DATE", nil]; NSFetchRequest *fetchRequest = [model fetchRequestFromTemplateWithName:@"PublicationsForAuthorSinceDate" substitutionVariables:substitutionDictionary]; NSArray *results = [aManagedObjectContext executeFetchRequest:fetchRequest error:&error]; |
* 如果模板中没有可替换的参数,你必须用以下两种方法之一
Use fetchRequestFromTemplateWithName:substitutionVariables: and pass nil as the variables argument; or
Use fetchRequestTemplateForName: and copy the result.
If you try to use the fetch request returned by fetchRequestTemplateForName:, this generates an exception (“Can’t modify a named fetch request in an immutable model”).
NSFetchedResultsControllerDelegate 协议,当results controller的结果集增加,修改,删除记录时会触发这些代理方法
– controllerWillChangeContent:
– controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
– controller:didChangeSection:atIndex:forChangeType:
– controllerDidChangeContent:
NSFetchedResultsController和UITableView的结合
fetch时设置sectionNameKeyPath,用于在table view中显示index title
1 | self.results = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:@"section" cacheName:@"Root"]; |
以下是table view相关的delegate方法,其中
tableView:titleForHeaderInSection:
sectionIndexTitlesForTableView:
和设置index title有关
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // Retrieve or create a cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"basic cell"]; if (!cell) cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"basic cell"] autorelease]; // Recover object from fetched results NSManagedObject *managedObject = [self.results objectAtIndexPath:indexPath]; cell.textLabel.text = [managedObject valueForKey:@"name"]; UIColor *color = [self getColor:[managedObject valueForKey:@"color"]]; cell.textLabel.textColor = ([[managedObject valueForKey:@"color"] hasPrefix:@"FFFFFF"]) ? [UIColor blackColor] : color; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // When a row is selected, color the navigation bar accordingly NSManagedObject *managedObject = [self.results objectAtIndexPath:indexPath]; UIColor *color = [self getColor:[managedObject valueForKey:@"color"]]; self.navigationController.navigationBar.tintColor = color; } #pragma mark Sections - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Use the fetched results section count return [[self.results sections] count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the count for each section return [[[self.results sections] objectAtIndex:section] numberOfObjects]; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView { // Return the array of section index titles return self.results.sectionIndexTitles; } - (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section { // Return the title for a given section NSArray *titles = [self.results sectionIndexTitles]; if (titles.count <= section) return @"Error"; return [titles objectAtIndex:section]; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { // Query the titles for the section associated with an index title return [self.results.sectionIndexTitles indexOfObject:title]; } |
条件查询
1 |
table view edit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { // delete request if (editingStyle == UITableViewCellEditingStyleDelete) { NSError *error = nil; [self.context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]]; if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]); } // update buttons after delete action [self setBarButtonItems]; // update sections [self performFetch]; } |
让Core Data支持undo,redo
每个context有各自的undoManager
self.undoManager = self.context.undoManager;
添加记录
1 2 3 4 5 6 7 8 9 10 11 12 13 | [self.context.undoManager beginUndoGrouping]; // build a new item and set its action field ToDoItem *item = (ToDoItem *)[NSEntityDescription insertNewObjectForEntityForName:@"ToDoItem" inManagedObjectContext:self.context]; item.action = todoAction; item.sectionName = [[todoAction substringToIndex:1] uppercaseString]; // save the new item NSError *error; if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]); [self.context.undoManager endUndoGrouping]; [self.context.undoManager setActionName:@"Add"]; |
删除记录
1 2 3 4 5 6 7 8 9 10 11 12 | [self.context.undoManager beginUndoGrouping]; // delete request if (editingStyle == UITableViewCellEditingStyleDelete) { NSError *error = nil; [self.context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]]; if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]); } [self.context.undoManager endUndoGrouping]; [self.context.undoManager setActionName:@"Delete"]; |
注:ios undo/redo的指令默认是靠晃动设备完成的。ios模拟器上shake的快捷键是control+command+z
如果要禁止晃动产生undo
1 | application.applicationSupportsShakeToEdit = NO; |
更多undo信息,请阅读Using Undo on iPhone
相关源码:
C19-CoreData(来自The.iPhone.Developers.Cookbook,2nd)
CoreDataBooks
