把模型鏈接到視圖(Connecting the Model to Views)
在macOS上,Core Data與用戶界面被設(shè)計(jì)為通過Cocoa綁定工作蛤高。然后,Cocoa綁定在iOS上并不是用戶界面的一部分。在IOS中通過NSFetchedResultsController將模型(Core Data)與視圖(storyboards)鏈接在一起钓株。
創(chuàng)建一個(gè)結(jié)果控制器(Creating a Fetched Results Controller)
當(dāng)你使用一個(gè)基于表格視圖(UITableView)布局的Core Data時(shí),NSFetchedResultsController為你提供的數(shù)據(jù)通常由UItableViewController的實(shí)例將要使用時(shí)初始化陌僵。這個(gè)初始化可以在viewDidLoad或viewWillAppear:方法里轴合,再或者在視圖控制器的生命周期的另一個(gè)邏輯起點(diǎn)。
這是個(gè)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的實(shí)例里面拾弃,首先構(gòu)建一個(gè)讀取請(qǐng)求(NSFetchRequest)值桩,這是NSFetchRequestController的核心。注意豪椿,讀取請(qǐng)求含有一種特殊的描述符(NSSortDescriptor)奔坟。NSFetchRequestController至少需要一種描述符來控制數(shù)據(jù)顯示的順序。
在初始化讀取請(qǐng)求之后搭盾,你可以初始化一個(gè)NSFetchedResultsController的實(shí)例咳秉。獲取結(jié)果控制器(fetched results controller)在運(yùn)行之前,你需要一個(gè)NSFetchRequest的實(shí)例和一個(gè)托管對(duì)象上下文(NSManagedObjectContext)的參考鸯隅。sectionNameKeyPaht和cacheName屬性都是可選的澜建。
在獲取結(jié)果控制器(fetched results controller)初始化之后,將其分派給委托蝌以。當(dāng)數(shù)據(jù)發(fā)生變化時(shí)代理通知表格視圖控制器(UITableViewController)炕舵。通常情況下,表格視圖控制器(UITableViewController)也通過代理告知獲取結(jié)果控制器(fetched results controller)跟畅,以便任何時(shí)候當(dāng)數(shù)據(jù)發(fā)生改變時(shí)接收到回調(diào)咽筋。
接下來,開始通過調(diào)用NSFetchedRrsuletsController的performFetfch:方法徊件,這個(gè)調(diào)用檢索要顯示的初始數(shù)據(jù)奸攻,并且作為NSFetchedResultsController實(shí)例開始監(jiān)測(cè)托管上下文(managed object context)開始(原因)蒜危。
將獲取到的結(jié)果與表格視圖的數(shù)據(jù)源集成(Integrating the Fetched Results Controller with the Table View Data Source)
當(dāng)你在表格視圖里初始化獲取結(jié)果控制器(initialized fetched results controller)并且有了可以用于顯示的數(shù)據(jù)之后,你就可以把獲取結(jié)果的控制器(fetched results controller)和表格視圖的數(shù)據(jù)源相互整合(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
}
每個(gè)UITableViewDataSource的方法如上圖所示辐赞,與獲取結(jié)果控制器(fetched results controller)的集成是減少到一個(gè)單一的方法調(diào)用,是專門設(shè)計(jì)的表格視圖數(shù)據(jù)源整合硝训。
將數(shù)據(jù)更改與表格視圖通信(Communicationg Data Chagnes to the Table View)
除了更容易的整合Core Date和表格視圖數(shù)據(jù)源(UITableViewDataSource)响委,NSFetchResultsController在數(shù)據(jù)發(fā)生變化是還處理和表格視圖控制器(UITableViewController)的通信。通過實(shí)現(xiàn)NSFetchedResultsControllerDelegae協(xié)議來實(shí)施捎迫。
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個(gè)協(xié)議提供了當(dāng)?shù)讓訑?shù)據(jù)變化時(shí)自動(dòng)更新相關(guān)表格的方法晃酒。
添加的部分(Adding Sections)
到目前位置,一直使用的表格視圖都是只有一個(gè)seciton的單一表格窄绒,只有一個(gè)section的表格需要所有的數(shù)據(jù)用以展示(或者說只能展示一個(gè)真題的數(shù)據(jù))贝次。(接著前面的例子)如果你正在使用大量的員工對(duì)象數(shù)據(jù),那么將表格分成多個(gè)部分是非常有利的彰导。通過部門對(duì)員工進(jìn)行分組使員工名單更加易于管理蛔翅。如果沒有Core Data,一個(gè)具有多個(gè)部分的表格視圖將涉及數(shù)組嵌套數(shù)組的結(jié)構(gòu)位谋,或者更為復(fù)雜的數(shù)據(jù)結(jié)構(gòu)山析。使用Core Data可以簡(jiǎn)單方便的更改獲取結(jié)果控制器(fetched results controller)以達(dá)到目的。
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)")
}
}
在這個(gè)例子中添加一個(gè)NSSortDescriptor實(shí)例到NSFetchRequest實(shí)例掏父。把新的描述符SectionNameKeyPath作為相同的密匙來初始化NSFetchedResultsController笋轨。獲取結(jié)果控制器(fetched results controller)用這些將控制器(controller)初始化用來打斷并且分類排序成多個(gè)sections,因此要求請(qǐng)求密匙必須匹配赊淑。
這個(gè)更改會(huì)讓獲取結(jié)果控制器(fetched results controller)根據(jù)每個(gè)實(shí)例關(guān)聯(lián)的部門名稱將返回的人員實(shí)例分成多個(gè)部分爵政。使用此功能的條件是:
- sectionNameKeyPath 的屬性也必須是一個(gè)NSSortDescriptor的實(shí)例。(The sectionNameKeyPath property must also be an NSSortDescriptor instance)
- NSSortDescriptor在請(qǐng)求數(shù)組中必須是第一個(gè)描述符陶缺。(The NSSortDescriptor must be the first descriptor in the array passed to the fetch request)
為性能添加緩存(Adding Caching for Performance)
在許多情況下钾挟,表格視圖(tableView)表示相對(duì)靜態(tài)的數(shù)據(jù)類型。在表格視圖控制器中定義了一個(gè)獲取請(qǐng)求饱岸,它在整個(gè)應(yīng)用程序的生命周期中不會(huì)改變掺出。在這種情況下,它有利于增加緩存(cache)的NSFetchedResultsController實(shí)例苫费,以便于在數(shù)據(jù)沒有改變的時(shí)候應(yīng)用程序一遍又一遍的啟動(dòng)所引起的表格視圖的突然初始化汤锨。緩存對(duì)于顯示異常大的數(shù)據(jù)時(shí)非常有用。
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")
如上圖所示百框,當(dāng)NSFetchedResultsController實(shí)例初始化設(shè)置cacheName屬性的時(shí)候泥畅,獲取結(jié)果的控制器(fetched results controller)自動(dòng)獲得緩存增益,隨后加載的數(shù)據(jù)幾乎瞬間完成。
注
如果發(fā)生請(qǐng)求伴隨著獲取結(jié)果控制器(fetched results controller)需要改變的情況位仁,那么在更改獲取控制器(fetched results controller)之前使緩存失效是至關(guān)重要的。你可以通過調(diào)用deleteCacheWithName:的方法使緩存(cache)失效方椎,這是一個(gè)類級(jí)別的方法在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.