使用 NSFetchedResultsController 的核心数据:更改后更新数据

Posted

技术标签:

【中文标题】使用 NSFetchedResultsController 的核心数据:更改后更新数据【英文标题】:Core Data with NSFetchedResultsController: Updating Data After Changes 【发布时间】:2018-02-21 06:44:53 【问题描述】:

在过去的 18 个小时左右,我一直在使用 Core Data。我正在使用NSFetchedResultsController 获取数据并使用UITableView 显示数据。添加新记录和删除所选记录不是我的问题。

class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate 
    // MARK: - Instance variables
    private let persistentContainer = NSPersistentContainer(name: "Profiles") // core data model file (.xcdatamodeld)
    var managedObjectContext: NSManagedObjectContext?

    // MARK: - IBOutlets
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() 
        super.viewDidLoad()

        // loading persistentContainer //
        persistentContainer.loadPersistentStores  (persistentStoreDescription, error) in
            if let error = error 
                print("Unable to Load Persistent Store")
             else 
                do 
                    try self.fetchedResultsController.performFetch()
                 catch 
                    let fetchError = error as NSError
                    print("\(fetchError), \(fetchError.localizedDescription)")
                
            
        

        // notifications //
        NotificationCenter.default.addObserver(self, selector: #selector(profileDidUpdate), name: NSNotification.Name(rawValue: "HomeViewControllerPictureDidSelect"), object: nil)
    

    // MARK: - fetchedResultsController(controller with the entity)
    fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Person> = 
        // Create Fetch Request with Entity
        let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()

        // Configure Fetch Request
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "lastName", ascending: true)]

        // Create Fetched Results Controller
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)

        // Configure Fetched Results Controller
        fetchedResultsController.delegate = self
        return fetchedResultsController
    ()
    // MARK: - fetchedResultsController


    // MARK: - Notifications
    @objc func profileDidUpdate(notification: NSNotification) 
        let profile = notification.object as! Profile
        let context = persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Person", in: context)
        let newPerson = NSManagedObject(entity: entity!, insertInto: context)
        newPerson.setValue(profile.uuid, forKey: "uuid") // uuid is used to make each record unique
        newPerson.setValue(profile.firstName, forKey: "firstName")
        newPerson.setValue(profile.lastName, forKey: "lastName")
        newPerson.setValue(profile.age, forKey: "age")
        newPerson.setValue(profile.pictData, forKey: "pictData")
        do 
            try context.save()
            print("saved...")
         catch 
            print("failed saving")
        
    
    // MARK: - Notifications


extension HomeViewController: NSFetchedResultsControllerDelegate 
    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) 
        tableView.beginUpdates()
    

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) 
        tableView.endUpdates()
    

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) 
        switch (type) 
        case .insert:
            if let indexPath = newIndexPath 
                tableView.insertRows(at: [indexPath], with: .fade)
            
            break;
        case .delete:
            if let indexPath = indexPath 
                tableView.deleteRows(at: [indexPath], with: .fade)
            
            break;
        default:
            print("...")
        
    

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) 
    

如上所示,我从另一个视图控制器创建了一条新记录,它将模型(Profile)的对象发送到当前视图控制器(HomeViewController)。感谢NSFetchedResultsController,我不必重新加载表格。

实体有几个属性(年龄、名字、姓氏、pictData、uuid)。我想用两个属性更改列表中的选定记录:名字和姓氏。 uuid 属性用于标识特定记录。

class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate 
    @IBAction func editTapped(_ sender: UIButton) 
        guard let indexPath = tableView.indexPathForSelectedRow else 
            return
        
        let selectedRow = indexPath.row
        if selectedRow >= 0 
            editRecord(index: selectedRow)
        
    

    func editRecord(index: Int) 
        let indexPath = IndexPath(row: index, section: 0)
        let person = fetchedResultsController.object(at: indexPath)
        let uuid = person.uuid!
        let context = self.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
        fetchRequest.predicate = NSPredicate(format: "uuid == %@", uuid)
        do 
            let result = try context.fetch(fetchRequest)
            if (result.count > 0) 
                let managedObject = result[0] as! NSManagedObject
                managedObject.setValue("Donald", forKey: "firstName")
                managedObject.setValue("Washington", forKey: "lastName")
                try context.save()
                print("Changes saved...")
            
         catch 
            print("Failed")
        
    

现在,如果我点击编辑按钮,应用程序不会立即更新列表。如果我重新启动应用程序,我会看到更改。那么,当我对所选记录进行更改时,如何使用NSFetchedResultsController 更新表?谢谢。

【问题讨论】:

profileDidUpdate 通知的目的是什么?您为此添加了一个观察者,但该通知是从哪里发送的? @Sam Fischer 我说过它用于从另一个视图控制器添加新记录。 既然您使用的是NSFetchedResultsControllerDelegate,您不应该在您的didChange 方法中处理NSFetchedResultsChangeType.update 的情况吗?现在,您只处理.insert。和.delete 案例。 @Sam Fischer 看起来像。我会调查的。谢谢。 @Sam Fischer 我已将 case .update 添加到控制器的 didChange 委托方法中。如果您在这方面发布解决方案,我会接受它作为答案。感谢您的帮助。 【参考方案1】:

由于您使用的是NSFetchedResultsControllerDelegate,因此您需要在didChange 方法中处理(针对您的特定用例)NSFetchedResultsChangeType 的以下情况:

插入 删除 更新

您的函数应该如下所示:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) 
  switch (type) 
  case .insert:
      if let indexPath = newIndexPath 
          tableView.insertRows(at: [indexPath], with: .fade)
      
      break;
  case .delete:
      if let indexPath = indexPath 
          tableView.deleteRows(at: [indexPath], with: .fade)
      
      break;
  case .update:
      tableView.reloadRows(at: [indexPath], with: .automatic)
      break;
  default:
      print("...")
  

【讨论】:

Sam,即使我进行了更改,该应用程序偶尔也会进入默认状态。你知道为什么吗?谢谢。 嗯,如果NSFetchedResultsChangeType.move,就会发生这种情况。我不知道为什么会这样。你能再描述一下这种行为吗?当您多次单击更新或...时会发生这种情况吗? 我只是输入修改后的值,如 func editRecord() 所示。有时,它会默认。如果我在默认情况下输入 tableView.reloadData(),一切似乎都正常。 如果您的UITableView 中的行数不多,那么我认为这不是问题。我很好奇为什么会出现这种行为,但我现在是一台 Linux 机器,因此无法自己编写测试应用程序。如果您最终弄清楚为什么会发生这种行为,请更新帖子。 没问题。谢谢,山姆。

以上是关于使用 NSFetchedResultsController 的核心数据:更改后更新数据的主要内容,如果未能解决你的问题,请参考以下文章

核心数据保存竞争条件错误

在 Swift 3 中难以配置 NSFetchedResultsController

为啥 beginUpdates/endUpdates 会重置表视图位置以及如何阻止它这样做?

测试使用

第一篇 用于测试使用

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?