Connecting the Model to Views
在os x中,core data被设计为通过cocoa绑定同user interface来交互。但是,cocoa 绑定并不是user interface中的一部分。在ios中,你使用NSFetchedResultsController
来将model(core data)和views(storyboards)来连接。
NSFetchedResultsController
提供了core data和UITableView
对象之间的接口。因为table views是最常见的展示数据的方式之一,uitableview被设计为处理数据的展示。
Creating a Fetched Results Controller
典型的,一个NSFetchedResultsController
实例会被UITableViewController
实例来初始化。这个初始化可以在viewDidLoad
或者viewWillAppear
方法中被实现。或者在view controller的其它地方被初始化。这个例子展示了NSFetchedResultsController
的初始化方法。
@property (nonatomic,strong) NSFetchedResultsController*fetchedResultsController;
- (void)initializeFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequestfetchRequestWithEntityName:@"Person"];
NSSortDescriptor *lastNameSort = [NSSortDescriptor sortDescriptorWithKey:@"lastName"ascending:YES];
[requestsetSortDescriptors:@[lastNameSort]];
NSManagedObjectContext *moc = …;//Retrieve the main queue NSManagedObjectContext
[self setFetchedResultsController:[[NSFetchedResultsControlleralloc] initWithFetchRequest:requestmanagedObjectContext:moc sectionNameKeyPath:nil cacheName:nil]];
[[selffetchedResultsController] setDelegate:self];
NSError *error= nil;
if (![[selffetchedResultsController] performFetch:&error]){
NSLog(@"Failed to initialize FetchedResultsController: %@\n%@",[error localizedDescription],[error userInfo]);
abort();
}
}
var fetchedResultsController:NSFetchedResultsController!
func initializeFetchedResultsController() {
let request =NSFetchRequest(entityName: "Person")
let departmentSort =NSSortDescriptor(key: "department.name", ascending: true)
let lastNameSort =NSSortDescriptor(key: "lastName", ascending: true)
request.sortDescriptors = [departmentSort,lastNameSort]
let moc =dataController.managedObjectContext
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc,sectionNameKeyPath: "department.name",cacheName: "rootCache")
fetchedResultsController.delegate =self
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to initialize FetchedResultsController:\(error)")
}
}
首先要构造一个fetch请求,
NSFetchRequest
,这位于NSFetchedResultsController
的重要位置。注意fetch请求包含一个排序描述,NSSortDescriptor
。NSFetchedResultsController
至少需要一个排序descriptor来控制将要呈现数据的显示顺序。
一旦fetch请求被初始化了,你就可以初始化NSFetchedResultsController
实例了。fetched results controller会要求你传给他一个NSFetchRequest
实例和NSFetchRequest
对象引用。sectionNameKeyPath
和cacheName
属性都是可选的。
一旦fetched results controller被初始化了,你就分配一个 i 饿delegate。这个delegate会在有数据变化时通知table view controller。重要的是,table view controller也是fetched results controller的delegate,因此当有数据变化时,他可以被回调。
然后,你通过performFetch:
方法来启动NSFetchedResultsController
。
Integrating the Fetched Results Controller with the Table View Data Source
在你初始化完成并且有数据需要展示在table view的时候,你需要将fetched results controller同table view data source UITableViewDataSource来
归整为一。
objc
#pragma mark - UITableViewDataSource
- (void)configureCell:(id)cellatIndexPath:(NSIndexPath*)indexPath
{
id object= [[self fetchedResultsController] objectAtIndexPath:indexPath];
// Populate cell from the NSManagedObject instance
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
id cell= [tableViewdequeueReusableCellWithIdentifier:CellReuseIdentifier];
// Set up the cell
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
{
return [[[selffetchedResultsController] sections] count];
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
id< NSFetchedResultsSectionInfo> sectionInfo= [[self fetchedResultsController] sections][section];
return [sectionInfonumberOfObjects];
}
swift
func configureCell(cell:UITableViewCell, indexPath: NSIndexPath) {
guard letselectedObject = fetchedResultsController.objectAtIndexPath(indexPath)as? AAAEmployeeMO else { fatalError("Unexpected Object in FetchedResultsController") }
// Populate cell from the NSManagedObject instance
print("Object for configuration:\(object)")
}
override functableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell =tableView.dequeueReusableCellWithIdentifier("cellIdentifier",forIndexPath: indexPath)
// Set up the cell
configureCell(cell,indexPath: indexPath)
return cell
}
override funcnumberOfSectionsInTableView(tableView:UITableView) -> Int {
return fetchedResultsController.sections!.count
}
override functableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard letsections = fetchedResultsController.sectionselse {
fatalError("No sections in fetchedResultsController")
}
let sectionInfo =sections[section]
return sectionInfo.numberOfObjects
}
正如在如上的UITableViewDataSource
方法中所展示的,fetched results controller和table view的关联都设计为单一的方法调用来完成。
Communicating Data Changes to the Table View
NSFetchedResultsController
还掌控了当有数据变化时,同UITableViewController
的通讯。实现NSFetchedResultsControllerDelegate
即可
objc
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller
{
[[selftableView] beginUpdates];
}
- (void)controller:(NSFetchedResultsController*)controllerdidChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfoatIndex:(NSUInteger)sectionIndexforChangeType:(NSFetchedResultsChangeType)type
{
switch(type){
case NSFetchedResultsChangeInsert:
[[selftableView] insertSections:[NSIndexSetindexSetWithIndex:sectionIndex]withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[selftableView] deleteSections:[NSIndexSetindexSetWithIndex:sectionIndex]withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate:
break;
}
}
- (void)controller:(NSFetchedResultsController*)controllerdidChangeObject:(id)anObjectatIndexPath:(NSIndexPath*)indexPath forChangeType:(NSFetchedResultsChangeType)typenewIndexPath:(NSIndexPath*)newIndexPath
{
switch(type){
case NSFetchedResultsChangeInsert:
[[selftableView] insertRowsAtIndexPaths:@[newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[selftableView] deleteRowsAtIndexPaths:@[indexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[[self tableView] cellForRowAtIndexPath:indexPath]atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[[selftableView] deleteRowsAtIndexPaths:@[indexPath]withRowAnimation:UITableViewRowAnimationFade];
[[selftableView] insertRowsAtIndexPaths:@[newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller
{
[[selftableView] endUpdates];
}
swift
func controllerWillChangeContent(controller:NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller:NSFetchedResultsController, didChangeSectionsectionInfo: NSFetchedResultsSectionInfo,atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .Insert:
tableView.insertSections(NSIndexSet(index:sectionIndex), withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(NSIndexSet(index:sectionIndex), withRowAnimation: .Fade)
case .Move:
break
case .Update:
break
}
}
func controller(controller:NSFetchedResultsController, didChangeObjectanObject: AnyObject, atIndexPath indexPath: NSIndexPath?,forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!],withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!],withRowAnimation: .Fade)
case .Update:
configureCell(tableView.cellForRowAtIndexPath(indexPath!)!,indexPath: indexPath!)
case .Move:
tableView.moveRowAtIndexPath(indexPath!,toIndexPath: newIndexPath!)
}
}
func controllerDidChangeContent(controller:NSFetchedResultsController) {
tableView.endUpdates()
}
Adding Sections
目前为止,table view只有一个section,如果需要使用多个Employee对象,可以划分多个section。
objc
- (void)initializeFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequestfetchRequestWithEntityName:@"Person"];
NSSortDescriptor *departmentSort = [NSSortDescriptor sortDescriptorWithKey:@"department.name"ascending:YES];
NSSortDescriptor *lastNameSort = [NSSortDescriptor sortDescriptorWithKey:@"lastName"ascending:YES];
[requestsetSortDescriptors:@[departmentSort,lastNameSort]];
NSManagedObjectContext *moc = [[selfdataController] managedObjectContext];
[self setFetchedResultsController:[[NSFetchedResultsControlleralloc] initWithFetchRequest:requestmanagedObjectContext:moc sectionNameKeyPath:@"department.name" cacheName:nil]];
[[selffetchedResultsController] setDelegate:self];
swift
func initializeFetchedResultsController() {
let request =NSFetchRequest(entityName: "Person")
let departmentSort =NSSortDescriptor(key: "department.name", ascending: true)
let lastNameSort =NSSortDescriptor(key: "lastName", ascending: true)
request.sortDescriptors = [departmentSort,lastNameSort]
let moc =dataController.managedObjectContext
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc,sectionNameKeyPath: "department.name",cacheName: nil)
fetchedResultsController.delegate =self
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to initialize FetchedResultsController:\(error)")
}
}
这个例子中你为NSFetchRequest
实例添加了一个NSSortDescriptor
实例。fetched results controller使用这个初始化排序controller来将数据分隔为不同的sections,因此需要keys来匹配。
这个变化使得fetched results controller将Person实例来分隔为多个sections。唯一的条件是:
-
sectionNameKeyPath
属性必须也是一个NSSortDescriptor
实例。 -
NSSortDescriptor
必须是数组中的第一个descriptor被传递给fetch request。
Adding Caching for Performance
大多数情况下,table view会展示相对固定的数据。在整个程序的运行周期内,创建table view controller时的fetch request不会发生变化。使用NSFetchedResultsController
添加缓存可以在程序再次启动的时候而且数据不发生改变,table view会即刻初始化。这个缓存对于大数据尤为有用。
[self setFetchedResultsController:[[NSFetchedResultsControlleralloc] initWithFetchRequest:requestmanagedObjectContext:moc sectionNameKeyPath:@"department.name" cacheName:@"rootCache"]];
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc,sectionNameKeyPath: "department.name",cacheName: "rootCache")
如上这样后续的数据加载就可以一瞬间完成。
注意:如果数据改变了,那么你如果需要清除缓存就直接调用deleteCacheWithName:
即可。