把模型链接到视图(Connecting the Model to Views)
在macOS上,Core Data与用户界面被设计为通过Cocoa绑定工作。然后,Cocoa绑定在iOS上并不是用户界面的一部分。在IOS中通过NSFetchedResultsController将模型(Core Data)与视图(storyboards)链接在一起。
创建一个结果控制器(Creating a Fetched Results Controller)
当你使用一个基于表格视图(UITableView)布局的Core Data时,NSFetchedResultsController为你提供的数据通常由UItableViewController的实例将要使用时初始化。这个初始化可以在viewDidLoad或viewWillAppear:方法里,再或者在视图控制器的生命周期的另一个逻辑起点。
这是个NSFetchedResultsController初始化的例子:
Objective-C:
@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
- (void)initializeFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
NSSortDescriptor *lastNameSort = [NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES];
[request setSortDescriptors:@[lastNameSort]];
NSManagedObjectContext *moc = …; //Retrieve the main queue NSManagedObjectContext
[self setFetchedResultsController:[[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:nil cacheName:nil]];
[[self fetchedResultsController] setDelegate:self];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(@"Failed to initialize FetchedResultsController: %@\n%@", [error localizedDescription], [error userInfo]);
abort();
}
}
Swift
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: nil, cacheName: nil)
fetchedResultsController.delegate = self
上面所示的初始化(initiallizeFetchedResultsController)方法在UITableViewController的实例里面,首先构建一个读取请求(NSFetchRequest),这是NSFetchRequestController的核心。注意,读取请求含有一种特殊的描述符(NSSortDescriptor)。NSFetchRequestController至少需要一种描述符来控制数据显示的顺序。
在初始化读取请求之后,你可以初始化一个NSFetchedResultsController的实例。获取结果控制器(fetched results controller)在运行之前,你需要一个NSFetchRequest的实例和一个托管对象上下文(NSManagedObjectContext)的参考。sectionNameKeyPaht和cacheName属性都是可选的。
在获取结果控制器(fetched results controller)初始化之后,将其分派给委托。当数据发生变化时代理通知表格视图控制器(UITableViewController)。通常情况下,表格视图控制器(UITableViewController)也通过代理告知获取结果控制器(fetched results controller),以便任何时候当数据发生改变时接收到回调。
接下来,开始通过调用NSFetchedRrsuletsController的performFetfch:方法,这个调用检索要显示的初始数据,并且作为NSFetchedResultsController实例开始监测托管上下文(managed object context)开始(原因)。
将获取到的结果与表格视图的数据源集成(Integrating the Fetched Results Controller with the Table View Data Source)
当你在表格视图里初始化获取结果控制器(initialized fetched results controller)并且有了可以用于显示的数据之后,你就可以把获取结果的控制器(fetched results controller)和表格视图的数据源相互整合(UITabaleViewDataSource)。
Objective-C
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id cell = [tableView dequeueReusableCellWithIdentifier:CellReuseIdentifier];
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
// Configure the cell from the object
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[[self fetchedResultsController] sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id< NSFetchedResultsSectionInfo> sectionInfo = [[self fetchedResultsController] sections][section];
return [sectionInfo numberOfObjects];
}
Swift
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) else {
fatalError("Wrong cell type dequeued")
}
// Set up the cell
guard let object = self.fetchedResultsController?.object(at: indexPath) else {
fatalError("Attempt to configure cell without a managed object")
}
//Populate the cell from the object
return cell
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController.sections!.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = fetchedResultsController.sections else {
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
每个UITableViewDataSource的方法如上图所示,与获取结果控制器(fetched results controller)的集成是减少到一个单一的方法调用,是专门设计的表格视图数据源整合。
将数据更改与表格视图通信(Communicationg Data Chagnes to the Table View)
除了更容易的整合Core Date和表格视图数据源(UITableViewDataSource),NSFetchResultsController在数据发生变化是还处理和表格视图控制器(UITableViewController)的通信。通过实现NSFetchedResultsControllerDelegae协议来实施。
Objective-C
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[[self tableView] beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[[self tableView] insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[self tableView] deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate:
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
switch(type) {
case NSFetchedResultsChangeInsert:
[[self tableView] insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[self tableView] deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[[self tableView] deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[[self tableView] insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[[self tableView] endUpdates];
}
Swift
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
case .move:
break
case .update:
break
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .fade)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
tableView.reloadRows(at: [indexPath!], with: .fade)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
上面的4个协议提供了当底层数据变化时自动更新相关表格的方法。
添加的部分(Adding Sections)
到目前位置,一直使用的表格视图都是只有一个seciton的单一表格,只有一个section的表格需要所有的数据用以展示(或者说只能展示一个真题的数据)。(接着前面的例子)如果你正在使用大量的员工对象数据,那么将表格分成多个部分是非常有利的。通过部门对员工进行分组使员工名单更加易于管理。如果没有Core Data,一个具有多个部分的表格视图将涉及数组嵌套数组的结构,或者更为复杂的数据结构。使用Core Data可以简单方便的更改获取结果控制器(fetched results controller)以达到目的。
Objective-C
- (void)initializeFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
NSSortDescriptor *departmentSort = [NSSortDescriptor sortDescriptorWithKey:@"department.name" ascending:YES];
NSSortDescriptor *lastNameSort = [NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES];
[request setSortDescriptors:@[departmentSort, lastNameSort]];
NSManagedObjectContext *moc = [[self dataController] managedObjectContext];
[self setFetchedResultsController:[[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:@"department.name" cacheName:nil]];
[[self fetchedResultsController] 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)")
}
}
在这个例子中添加一个NSSortDescriptor实例到NSFetchRequest实例。把新的描述符SectionNameKeyPath作为相同的密匙来初始化NSFetchedResultsController。获取结果控制器(fetched results controller)用这些将控制器(controller)初始化用来打断并且分类排序成多个sections,因此要求请求密匙必须匹配。
这个更改会让获取结果控制器(fetched results controller)根据每个实例关联的部门名称将返回的人员实例分成多个部分。使用此功能的条件是:
- sectionNameKeyPath 的属性也必须是一个NSSortDescriptor的实例。(The sectionNameKeyPath property must also be an NSSortDescriptor instance)
- NSSortDescriptor在请求数组中必须是第一个描述符。(The NSSortDescriptor must be the first descriptor in the array passed to the fetch request)
为性能添加缓存(Adding Caching for Performance)
在许多情况下,表格视图(tableView)表示相对静态的数据类型。在表格视图控制器中定义了一个获取请求,它在整个应用程序的生命周期中不会改变。在这种情况下,它有利于增加缓存(cache)的NSFetchedResultsController实例,以便于在数据没有改变的时候应用程序一遍又一遍的启动所引起的表格视图的突然初始化。缓存对于显示异常大的数据时非常有用。
Objective-C
[self setFetchedResultsController:[[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:@"department.name" cacheName:@"rootCache"]];
Swift
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc, sectionNameKeyPath: "department.name", cacheName: "rootCache")
如上图所示,当NSFetchedResultsController实例初始化设置cacheName属性的时候,获取结果的控制器(fetched results controller)自动获得缓存增益,随后加载的数据几乎瞬间完成。
注
如果发生请求伴随着获取结果控制器(fetched results controller)需要改变的情况,那么在更改获取控制器(fetched results controller)之前使缓存失效是至关重要的。你可以通过调用deleteCacheWithName:的方法使缓存(cache)失效,这是一个类级别的方法在NSFetchedResultsController里。
原文:If a situation occurs where the fetch request associated with a fetched results controller needs to change, then it is vital that the cache be invalidated prior to changing the fetched results controller. You invalidate the cache by calling deleteCacheWithName:, which is a class level method on NSFetchedResultsController.