美文网首页iOS DeveloperiOS进阶指南iOS 开发
2.0 连接模型到视图 (Core Data Programmi

2.0 连接模型到视图 (Core Data Programmi

作者: 西蒙SIMON | 来源:发表于2016-08-24 10:01 被阅读97次

    这是苹果官方文档 Core Data Programming Guide 的渣翻译。

    在OS X系统中,Core Data设计目的在于通过Cocoa bindings在用户接口发挥作用。然而,Cocoa bindings并不是iOS用户接口的一部分。在iOS中,你需要使用NSFetchedResultsController去连接模型(Core Data)和视图(storyboard)。

    NSFetchedResultsController提供了Core Data和UITableView对象之间的接口。因为table视图是在iOS中用得最多的用以展示数据的方式,UITableView几乎处理了所有大量数据集合的显示。

    创建一个Fetched Result Controller

    一般来讲,一个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 = 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实例运行的时候能一直存活的initializeFetchedResultsController方法中,你第一步构建了一个Fetch请求(NSFetchRequest),Fetch请求是这个NSFetchedResultsController的中心。注意这个Fetch请求包含了一个排序描述器(NSSortDescriptor)。NSFetchedResultsController需要至少一个排序描述器来控制展示出来的数据顺序。

    一旦Fetch请求被初始化,你就可以初始化NSFetchedResultsController实例了。Fetched Results Controller需要你传递一个NSFetchRequest实例和一个托管对象上下文实例(NSManagedObjectContext)来运行操作。sectionNameKeyPath和cacheName属性都是可选的。

    一旦Fetched Results Controller初始化了,你就可以设置它的代理(delegate)。这个代理会通知table view有关任何的数据结构的变化。一般来说,table view同时也是Fetched Results Controller的代理,所以能够在相关数据发生变化的时候调用回调。

    下一步,你可以开始调用performFetch:来使用NSFetchedResultsController。这个调用会查询初始数据用以展示,并触发NSFetchedResultsController实例开始监控托管对象上下文MOC的变化。

    集成Fetched Results Controller 和 Table View Data Source

    在你集成了已初始化的fetched results controller和准备好了要展示在table view上的数据之后,你需要集成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
    }
    

    就如上面每个UITableViewDataSource的方法展示的那样,fetched results controller的集成接口减少到了只需要一个就能跟table view data source进行集成了。

    传递数据更新到Table View

    除了更加容易集成Core Data和table view data source之外,NSFetchedResultsController还能在数据发生改变的时候跟UITableViewController实例通信。为了实现这个功能,需要实现 NSFetchedResultsControllerDelegate协议:

    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()
    }
    

    实现这4个上述的协议方法就能够自动更新UITableView,无论何时相关数据的更新都能够触发更新。

    添加Sections

    到目前为止你可以已经使用一个仅有一个sectiion的table view,这个setion展示了所有table view需要展示的数据。如果你要操作拥有更多数据的Employee对象集合,那么把这个table view分成多个section更好。把Employee按照department来分组更加利于管理。如果不使用Core Data,实现一个带有多个section的table view可以解析一个含有多个数组的数组或更加复杂的数据结构。有了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)")
        }
    }
    

    在这个例子中你需要添加多一个NSSortDesctiptor实例到NSFetchRequest实例中。你要在NSFetchedResultsController的初始化中为sectionNameKeyPath参数设置跟这个新sort descriptor同样的key。fetched results controller使用这个初始化的controller并把数据分成多个section,因此所有的key都要匹配。

    这个修改让fetched results controller把返回的Person实例基于每个Person相关的department的name分成了多个section。唯一使用这个特性的条件是:

    • sectionNameKeyPath属性必须是一个NSSortDescriptor实例。
    • 上述NSSortDescriptor必须是传递给fetch request的数组中的第一个descriptor。

    为性能而添加的缓存

    在许多情况下,一个table view会表示同一种相近类型的数据。一个fetch request在table view controller创建的时候被定义,并且在应用的生命周期内不会改变。如果能给NSFetchedResultsController实例添加一个缓存,当应用重新启动而且数据没有改变的时候,这无疑是极好的,这样table view能迅速初始化。特别是对大型数据集,缓存特别有用。

    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参数在NSFetchedResultsController实例初始化的时候设置,fetched result controller会自动获得这个缓存。之后数据的加载几乎是瞬间的事。

    注意  
    如果一个fetched results controller相关的fetch request要发生改变,很重要一点就是在fetched results controller发生改变之前缓存必须无效化。你可以使用NSFetchedResultsController的类方法deleteCacheWithName:来无效化缓存。

    相关文章

      网友评论

        本文标题:2.0 连接模型到视图 (Core Data Programmi

        本文链接:https://www.haomeiwen.com/subject/bgnnsttx.html