插入具有多个 NSFetchedResultsController 的行

Posted

技术标签:

【中文标题】插入具有多个 NSFetchedResultsController 的行【英文标题】:Inserting rows with multiple NSFetchedResultsController's 【发布时间】:2015-03-23 00:36:28 【问题描述】:

我正在从 Internet 下载信息并使用这些数据在 Core Data 中创建实体。我正在尝试通过与TVShow 实体有关系的TVEpisode 实体的airDate 属性对实体(实体是电视节目,数据来自Trakt)进行排序。 TVShow 实体仅在节目数据中有一个在当前时间的未来日期播出的剧集时才与节目有这种关系。

所以我想对数据进行排序的方式是: 顶部:具有即将到来的剧集关系的节目,按upcomingEpisodeairDate 属性排序,升序排列。 中:没有upcomingEpisode 关系但将返回的节目。 底部:没有upcomingEpisode 关系且已结束/取消的节目

这是我在使其工作时遇到的问题。

问题 1:使用 1 NSFetchedResultsController

let fetchRequest = NSFetchRequest(entityName: "TVShow")
    let airDateSort = NSSortDescriptor(key: "upcomingEpisode.airDate", ascending: true)
    let titleSort = NSSortDescriptor(key: "title", ascending: true)
    fetchRequest.sortDescriptors = [airDateSort, titleSort];

    upcomingShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: "upcomingShows")
    upcomingShowsResultsController.delegate = self;

    var error: NSError? = nil
    if (!upcomingShowsResultsController.performFetch(&error)) 
        println("Error: \(error?.localizedDescription)")
    

使用这个NSFetchedResultsController 会将所有没有upcomingEpisode 关系的TVShow 实体放在最上面,按标题排序,我需要在最底部按标题排序的已死节目,并在中间返回按标题排序的节目.

问题 2:使用多个 NSFetchedResultsController's

func setupUpcomingShowsFetchedResultsController() 
    let fetchRequest = NSFetchRequest(entityName: "TVShow")
    let airDateSort = NSSortDescriptor(key: "upcomingEpisode.airDate", ascending: true)
    let titleSort = NSSortDescriptor(key: "title", ascending: true)
    fetchRequest.sortDescriptors = [airDateSort, titleSort];

    let predicate = NSPredicate(format: "upcomingEpisode != nil")
    fetchRequest.predicate = predicate

    upcomingShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: "upcomingShows")
    upcomingShowsResultsController.delegate = self;

    var error: NSError? = nil
    if (!upcomingShowsResultsController.performFetch(&error)) 
        println("Error: \(error?.localizedDescription)")
    


func setupReturningShowsFetchedResultsController() 
    let fetchRequest = NSFetchRequest(entityName: "TVShow")
    let titleSort = NSSortDescriptor(key: "title", ascending: true)
    fetchRequest.sortDescriptors = [titleSort];

    let predicate = NSPredicate(format: "status == 'returning series'")
    let predicate2 = NSPredicate(format: "upcomingEpisode == nil")
    fetchRequest.predicate = NSCompoundPredicate.andPredicateWithSubpredicates([predicate!, predicate2!])

    returningShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
    returningShowsResultsController.delegate = self;

    var error: NSError? = nil
    if (!returningShowsResultsController.performFetch(&error)) 
        println("Error: \(error?.localizedDescription)")
    


func setupDeadShowsFetchedResultsController() 
    let fetchRequest = NSFetchRequest(entityName: "TVShow")
    let titleSort = NSSortDescriptor(key: "title", ascending: true)
    fetchRequest.sortDescriptors = [titleSort]

    let endedShowsPredicate = NSPredicate(format: "status == 'ended'")
    fetchRequest.predicate = endedShowsPredicate

    deadShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
    deadShowsResultsController.delegate = self;

    var deadShowsError: NSError? = nil
    if (!deadShowsResultsController.performFetch(&deadShowsError)) 
        println("Error: \(deadShowsError?.localizedDescription)")
    

这些可以满足我的需求,但前提是数据已经下载并在 Core Data 中。当应用程序首次启动并下载数据时,它每次都会崩溃,因为一个部分中的行数与表的预期不同。我确实操纵了NSFetchedResultsControllerDelegatedidChangeObject 函数中提供的索引路径,并且我打印了正在插入的索引。我在任何部分所做的计数等于表视图所说的预期数量,但每次都会引发错误。这就是我处理多个NSFetchedResultsController's的方法

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) 
        let section = sectionOfFetchedResultsController(controller)
        let indexPathsComputed = [NSIndexPath(forRow: indexPath?.row ?? 0, inSection: section)]
        let newIndexPathsComputed = [NSIndexPath(forRow: newIndexPath?.row ?? 0, inSection: section)]

        dispatch_async(dispatch_get_main_queue(),  () -> Void in
            switch type 
            case NSFetchedResultsChangeType.Insert:
                self.tableView.insertRowsAtIndexPaths(newIndexPathsComputed, withRowAnimation: .Automatic)
            case NSFetchedResultsChangeType.Delete:
                self.tableView.deleteRowsAtIndexPaths(indexPathsComputed, withRowAnimation: .Automatic)
            case NSFetchedResultsChangeType.Move:
                self.tableView.deleteRowsAtIndexPaths(indexPathsComputed, withRowAnimation: .Automatic)
                self.tableView.insertRowsAtIndexPaths(newIndexPathsComputed, withRowAnimation: .Automatic)
            case NSFetchedResultsChangeType.Update:
                if let index = indexPathsComputed[0] 
                    if let cell = self.tableView.cellForRowAtIndexPath(index) as? ShowTableViewCell 
                        self.configureCell(cell, indexPath: index)
                    
                
                else 
                    println("No cell at index path")
                
            
        )
    

如果可以修复崩溃,这将是实现我想要做的最佳方式。

问题3:使用多个数组

func reloadShowsArray() 
    let fetchRequest = NSFetchRequest(entityName: "TVShow")
    let airDateSort = NSSortDescriptor(key: "upcomingEpisode.airDate", ascending: true)
    let titleSort = NSSortDescriptor(key: "title", ascending: true)
    fetchRequest.sortDescriptors = [airDateSort, titleSort];

    let predicate = NSPredicate(format: "upcomingEpisode != nil")
    fetchRequest.predicate = predicate

    var error: NSError?
    showsArray = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [TVShow]
    if let error = error 
        println(error)
    


func reloadDeadShows() 
        let fetchRequest = NSFetchRequest(entityName: "TVShow")
        let titleSort = NSSortDescriptor(key: "title", ascending: true)
        fetchRequest.sortDescriptors = [titleSort]

        let endedShowsPredicate = NSPredicate(format: "status == 'ended'")
        fetchRequest.predicate = endedShowsPredicate

        var error: NSError?
        deadShows = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [TVShow]
        if let error = error 
            println(error)
        
    

这解决了下载数据后和下载数据时的崩溃和工作。但是在使用这个时,我必须在数据下载时调用self.tableView.reloadData(),实体只是弹出到没有动画的表格视图中,我真的想要来自insertRowsAtIndexPaths的动画,因为它看起来更好并且是更好的体验.我尝试调用reloadShowsArray(),然后将find() 函数与实体一起使用来获取索引,这样我就可以使用insertRowsAtIndexPaths,但每次索引都返回nil,即使实体在此之前与上下文一起保存。此外,单元格不会像 NSFetchedResultsController 那样自动重新加载或移动。

那么处理这个问题的最佳方法是什么,我怎样才能通过动画获得所需的排序?

【问题讨论】:

对于选项 1,顶部显示的 status 是什么?您能否先按status 排序,以便以正确的顺序获得顶部/中间/底部?对于选项 2,您在其他 FRC 委托方法中有 tableView.beginUpdates() 和 tableView.endUpdates() 吗?如果不是,那可能会解释错误。即便如此,您可能需要实现一个计数器,该计数器随着每个controller:willChangeContent 调用而增加,并随着每个controller:didChangeContent 调用而减少。仅当计数器为零时才应调用 beginUpdates,仅当计数器返回零时才应调用 endUpdates。 同样在选项 2 中,错误可能与 dispatch_async 相关联,因为它可能导致表更新发生在 beginUpdates/endUpdates 周期之外。 ***节目的状态是“回归系列”,中间节目的状态是“制作中”,底部节目的状态是“结束”/“取消”。我不知道为什么,但按它排序并没有按照我以前想要的方式工作。我可以再试一次,但它有一个奇怪的排序。我也会尝试计数器,这可能会起作用.. 我在那里使用 dispatch_async 因为我认为它比在主线程的每个地方保存上下文更容易,因为在网络请求中它们不在主线程上线程,所以它只是确保它们被正确插入。 进行状态排序似乎无法正常工作。我只有状态排序,实体的顺序很奇怪。第一个是“即将推出”,然后是“回归系列”,然后是来回。然后变成了所有的“回归系列”。所以效果不好。 是的,我在这些方法中有 beginUpdates() 和 endUpdates() @pbasdf 【参考方案1】:

根据 cmets,我怀疑三 FRC 方法会导致问题,因为一个 FRC 调用 controller:didChangeContent(触发 tableView.endUpdates),而另一个 FRC 仍在处理更新。为了克服这个问题,实现一个在controller:willChangeContent 中递增并在controller:didChangeContent 中递减的计数器。 tableView beginUpdates 只应在计数器为零时调用,endUpdates 仅在计数器返回零时调用。这样,endUpdates 只会在所有三个 FRC 都完成处理它们的更新时才被调用。

如果可能,我也会避免使用dispatch_async,因为它可能导致表更新发生在 beginUpdates/endUpdates 周期之外。

【讨论】:

以上是关于插入具有多个 NSFetchedResultsController 的行的主要内容,如果未能解决你的问题,请参考以下文章

将数据插入多个 1:1 表,其中主表具有自动编号

使用具有多个值的 unordered_map 移动插入

使用 CodeIgniter 框架将数据插入到具有外键的多个表中

Mysql - 将值插入具有未知主键的多个表中

关系的 NSFetchedResultsController 委托回调

Java 中 MySQL 插入语句的性能:批处理模式准备语句与具有多个值的单个插入