這是蘋果官方文檔 Core Data Programming Guide 的渣翻譯。
在OS X系統(tǒng)中,Core Data設(shè)計(jì)目的在于通過Cocoa bindings在用戶接口發(fā)揮作用妓蛮。然而味悄,Cocoa bindings并不是iOS用戶接口的一部分荷憋。在iOS中肋演,你需要使用NSFetchedResultsController去連接模型(Core Data)和視圖(storyboard)术唬。
NSFetchedResultsController提供了Core Data和UITableView對(duì)象之間的接口薪伏。因?yàn)閠able視圖是在iOS中用得最多的用以展示數(shù)據(jù)的方式,UITableView幾乎處理了所有大量數(shù)據(jù)集合的顯示粗仓。
創(chuàng)建一個(gè)Fetched Result Controller
一般來講嫁怀,一個(gè)NSFetchedResultsController實(shí)例會(huì)被一個(gè)UITableViewController實(shí)例在需要的時(shí)候初始化。這個(gè)初始化過程可以發(fā)生在viewDidLoad或者viewWillAppear方法借浊,或者在其他視圖控制器任意一個(gè)生命周期中某個(gè)時(shí)間點(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 = self.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)")
}
}
在上述的蚂斤、在UITableViewController實(shí)例運(yùn)行的時(shí)候能一直存活的initializeFetchedResultsController方法中存捺,你第一步構(gòu)建了一個(gè)Fetch請(qǐng)求(NSFetchRequest),F(xiàn)etch請(qǐng)求是這個(gè)NSFetchedResultsController的中心曙蒸。注意這個(gè)Fetch請(qǐng)求包含了一個(gè)排序描述器(NSSortDescriptor)捌治。NSFetchedResultsController需要至少一個(gè)排序描述器來控制展示出來的數(shù)據(jù)順序。
一旦Fetch請(qǐng)求被初始化纽窟,你就可以初始化NSFetchedResultsController實(shí)例了肖油。Fetched Results Controller需要你傳遞一個(gè)NSFetchRequest實(shí)例和一個(gè)托管對(duì)象上下文實(shí)例(NSManagedObjectContext)來運(yùn)行操作。sectionNameKeyPath和cacheName屬性都是可選的臂港。
一旦Fetched Results Controller初始化了森枪,你就可以設(shè)置它的代理(delegate)。這個(gè)代理會(huì)通知table view有關(guān)任何的數(shù)據(jù)結(jié)構(gòu)的變化审孽。一般來說县袱,table view同時(shí)也是Fetched Results Controller的代理,所以能夠在相關(guān)數(shù)據(jù)發(fā)生變化的時(shí)候調(diào)用回調(diào)佑力。
下一步式散,你可以開始調(diào)用performFetch:來使用NSFetchedResultsController。這個(gè)調(diào)用會(huì)查詢初始數(shù)據(jù)用以展示打颤,并觸發(fā)NSFetchedResultsController實(shí)例開始監(jiān)控托管對(duì)象上下文MOC的變化杂数。
集成Fetched Results Controller 和 Table View Data Source
在你集成了已初始化的fetched results controller和準(zhǔn)備好了要展示在table view上的數(shù)據(jù)之后,你需要集成fetched results controller和table view的data source(UITableViewDataSource)瘸洛。
OBJECTIVE-C
#pragma mark - UITableViewDataSource
- (void)configureCell:(id)cell atIndexPath:(NSIndexPath*)indexPath
{
id object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// Populate cell from the NSManagedObject instance
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id cell = [tableView dequeueReusableCellWithIdentifier:CellReuseIdentifier];
// Set up the cell
[self configureCell:cell atIndexPath:indexPath];
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
func configureCell(cell: UITableViewCell, indexPath: NSIndexPath) {
let employee = fetchedResultsController.objectAtIndexPath(indexPath) as! AAAEmployeeMO
// Populate cell from the NSManagedObject instance
print("Object for configuration: \(object)")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath) as! UITableViewCell
// Set up the cell
configureCell(cell, indexPath: indexPath)
return cell
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController.sections!.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sections = fetchedResultsController.sections as! [NSFetchedResultsSectionInfo]
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
就如上面每個(gè)UITableViewDataSource的方法展示的那樣揍移,fetched results controller的集成接口減少到了只需要一個(gè)就能跟table view data source進(jìn)行集成了。
傳遞數(shù)據(jù)更新到Table View
除了更加容易集成Core Data和table view data source之外反肋,NSFetchedResultsController還能在數(shù)據(jù)發(fā)生改變的時(shí)候跟UITableViewController實(shí)例通信那伐。為了實(shí)現(xiàn)這個(gè)功能,需要實(shí)現(xiàn) NSFetchedResultsControllerDelegate協(xié)議:
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 configureCell:[[self tableView] cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
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) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: 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, didChangeObject anObject: NSManagedObject, 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(self.tableView.cellForRowAtIndexPath(indexPath!)!, indexPath: indexPath!)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
實(shí)現(xiàn)這4個(gè)上述的協(xié)議方法就能夠自動(dòng)更新UITableView,無論何時(shí)相關(guān)數(shù)據(jù)的更新都能夠觸發(fā)更新罕邀。
添加Sections
到目前為止你可以已經(jīng)使用一個(gè)僅有一個(gè)sectiion的table view畅形,這個(gè)setion展示了所有table view需要展示的數(shù)據(jù)。如果你要操作擁有更多數(shù)據(jù)的Employee對(duì)象集合诉探,那么把這個(gè)table view分成多個(gè)section更好日熬。把Employee按照department來分組更加利于管理。如果不使用Core Data肾胯,實(shí)現(xiàn)一個(gè)帶有多個(gè)section的table view可以解析一個(gè)含有多個(gè)數(shù)組的數(shù)組或更加復(fù)雜的數(shù)據(jù)結(jié)構(gòu)竖席。有了Core Data,你僅需要對(duì)fetched results controller的構(gòu)建做出一點(diǎn)小修改即可.
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è)NSSortDesctiptor實(shí)例到NSFetchRequest實(shí)例中敬肚。你要在NSFetchedResultsController的初始化中為sectionNameKeyPath參數(shù)設(shè)置跟這個(gè)新sort descriptor同樣的key毕荐。fetched results controller使用這個(gè)初始化的controller并把數(shù)據(jù)分成多個(gè)section,因此所有的key都要匹配艳馒。
這個(gè)修改讓fetched results controller把返回的Person實(shí)例基于每個(gè)Person相關(guān)的department的name分成了多個(gè)section憎亚。唯一使用這個(gè)特性的條件是:
- sectionNameKeyPath屬性必須是一個(gè)NSSortDescriptor實(shí)例。
- 上述NSSortDescriptor必須是傳遞給fetch request的數(shù)組中的第一個(gè)descriptor弄慰。
為性能而添加的緩存
在許多情況下第美,一個(gè)table view會(huì)表示同一種相近類型的數(shù)據(jù)。一個(gè)fetch request在table view controller創(chuàng)建的時(shí)候被定義陆爽,并且在應(yīng)用的生命周期內(nèi)不會(huì)改變什往。如果能給NSFetchedResultsController實(shí)例添加一個(gè)緩存,當(dāng)應(yīng)用重新啟動(dòng)而且數(shù)據(jù)沒有改變的時(shí)候墓陈,這無疑是極好的恶守,這樣table view能迅速初始化第献。特別是對(duì)大型數(shù)據(jù)集贡必,緩存特別有用。
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")
就如上面展示的庸毫,cacheName參數(shù)在NSFetchedResultsController實(shí)例初始化的時(shí)候設(shè)置仔拟,fetched result controller會(huì)自動(dòng)獲得這個(gè)緩存。之后數(shù)據(jù)的加載幾乎是瞬間的事飒赃。
注意
如果一個(gè)fetched results controller相關(guān)的fetch request要發(fā)生改變利花,很重要一點(diǎn)就是在fetched results controller發(fā)生改變之前緩存必須無效化。你可以使用NSFetchedResultsController的類方法deleteCacheWithName:來無效化緩存载佳。