使用多个 NSFetchedResultsController 插入行

Inserting rows with multiple NSFetchedResultsController's

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

所以我想对数据进行排序的方式是: 顶部:具有 upcomingEpisode 关系的节目,按 upcomingEpisodeairDate 属性排序,升序排列。 中间:显示没有 upcomingEpisode 关系但将返回。 底部:没有 upcomingEpisode 关系且 ended/cancelled

的节目

以下是我 运行 使它正常工作的问题。

问题 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 会将所有 TVShow 没有 upcomingEpisode 关系的实体放在最上面,按标题排序,我需要在最底部按标题排序死去的节目,返回中间按标题排序的节目。

问题 2:使用多个 NSFetchedResultsController

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

这些可以满足我的需求,但前提是数据已下载并位于核心数据中。当应用程序首次启动并下载数据时,它每次都会崩溃,因为一个部分中的行数与 table 预期的不一样。我确实操纵了 NSFetchedResultsControllerDelegatedidChangeObject 函数中给出的索引路径,并且打印出正在插入的索引。我在任何部分所做的计数等于 table 视图表示它期望的数量,但每次都会抛出错误。这就是我处理多个 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(),并且实体只是弹出 table 视图而没有动画,我真的想要来自 insertRowsAtIndexPaths 的动画,因为它看起来更好,是更好的体验。我尝试调用 reloadShowsArray(),然后将 find() 函数与实体一起使用以获取索引,这样我就可以使用 insertRowsAtIndexPaths,但每次索引时 returns nil,即使实体是在此之前与上下文一起保存。此外,单元格不会像 NSFetchedResultsController

那样自动重新加载或移动

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

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

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