IOS中Core Data的整合(Integrating Core Data with iOS)


把模型链接到视图(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.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容