這篇文章重點講講CoreData的Fetched Results Controller。
對應的objc類為NSFetchedResultsController。這個類是用來管理CoreData Fetch request傳回的對象的。
在建立這個控制器之前,必須先建立fetch request。 fetch request描述了詳細的查詢規則,還可以添加查詢結果的排序描述(sort descriptor)。fetchResultsController根據已經建立完的fetch request來建立, 它是NSFetchedResultsController的執行個體,這個執行個體的主要任務就是使用fetch request來保證它所關聯的資料的新鮮性。建立了fetchResultsController執行個體後要做一下初始化,一般初始化是向這個控制器發送PerformFetch消息,下面是這一過程的代碼。
- (NSFetchedResultsController *)fetchedResultsController { if (fetchedResultsController != nil) { return fetchedResultsController; } /* Set up the fetched results controller. */ // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; // Set the batch size to a suitable number. [fetchRequest setFetchBatchSize:20]; // Edit the sort key as appropriate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; [aFetchedResultsController release]; [fetchRequest release]; [sortDescriptor release]; [sortDescriptors release]; return fetchedResultsController; }
`這個函數用來建立FetchedResultsController,過程還是比較簡單的,下面是初始化這個控制器代碼。
NSError *error = nil; if(![[self fetchedResultsController]performFetch: &error]){ //handle the error appropriately NSLog(@"Unresolved error %@, %@", error, [error userInfo]); exit(-1); }
這段代碼一般會放在viewDidLoad函數中,初始化之後,fetchedResultsController就與資料相連接配接了,之後要取資料都能直接從這個控制器提供的方法中去取。
實作這個控制器,最關鍵的還要實作Fetched Results Controller Delegate Methods。控制器與資料源連接配接後,控制器螢幕會時刻監視着資料源,當資料源發生
改變後,螢幕會調用對應的協定方法,改協定總共要實作四個方法,分别為:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller; - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller; - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath; - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
下面依次來解釋這四個協定方法。
1. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
當控制器監控的資料發生改變時,如對象被删除,有插入,更新等,螢幕會在資料發生改變前意識到這個情況,此時就會調用這個函數。往往我們用清單的形式
表現資料,此時意味着螢幕上的資料即将過時,因為資料馬上要改變了,這是這個協定方法的工作就是通知清單資料馬上要更新的消息,往往代碼是這樣實作的。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { [self.tableView beginUpdates]; }
2. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
當fetchedResultsController完成對資料的改變時,螢幕會調用這個協定方法。在上面提到的情況,這個方法要通知清單資料已經完成,可以更新顯示的資料這個
消息,是以通常的實作是這樣的。
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
3. - (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
當fetchedResultsController發現指定的對象有改變時,螢幕會調用這個協定方法。這裡改變的類型從清單中展現有 更新、插入、删除或者行的移動。是以這個
方法要實作所有的這些方法,以應對任何一種改變。下面是這個方法的标準實作。
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
NSString *sectionKeyPath = [controller sectionNameKeyPath];
if (sectionKeyPath == nil)
break;
NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];
NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];
id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];
for (int i = 0; i < [keyParts count] - 1; i++) {
NSString *onePart = [keyParts objectAtIndex:i];
changedObject = [changedObject valueForKey:onePart];
}
sectionKeyPath = [keyParts lastObject];
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];
if ([[committedValues valueForKeyPath:sectionKeyPath]isEqual:currentKeyValue])
break;
NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (tableSectionCount != frcSectionCount) {
// Need to insert a section
NSArray *sections = controller.sections;
NSInteger newSectionLocation = -1;
for (id oneSection in sections) {
NSString *sectionName = [oneSection name];
if ([currentKeyValue isEqual:sectionName]) {
newSectionLocation = [sections indexOfObject:oneSection];
break;
}
}
if (newSectionLocation == -1)
return; // uh oh
if (!((newSectionLocation == 0) && (tableSectionCount == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation]
withRowAnimation:UITableViewRowAnimationFade];
NSUInteger indices[2] = {newSectionLocation, 0};
newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indiceslength:2] autorelease];
}
}
case NSFetchedResultsChangeMove
if (newIndexPath != nil) {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight];
}
else {
[self.tableView reloadSections:[NSIndexSet
indexSetWithIndex:[indexPath section]]withRowAnimation:UITableViewRowAnimationFade];
}
break;
default:
break;
}
}
從上面的代碼可以看出,插入,删除,移動是比較簡單的,最複雜的是更新。這個代碼是xcode的模闆代碼,基本能适用我們遇到的情況,對更新裡面的代碼我還不是非常确定,是以這裡留着等過幾天完全吃透了再補上。
4. - (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
當改變控制器管理的對象後引起了清單section的變化,此時螢幕就會調用這個協定函數。
下面是标準實作。
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate:
default:
break;
}
}