【问题标题】:Why tableView.reloadData() is not triggered after Core Data container.performBackgroundTask()为什么在 Core Data container.performBackgroundTask() 之后不触发 tableView.reloadData()
【发布时间】:2018-04-08 06:02:04
【问题描述】:

我正在使用 Swift 4 构建具有 UITableViewController 的单视图 iOS 11 应用程序,该应用程序也定义为 delegate 用于 NSFetchedResultsController

class MyTVC: UITableViewController, NSFetchedResultsControllerDeleagate {
    var container:NSPersistentContainer? = 
           (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer

   var frc : NSFetchedResultsController<Student>? 

   override func viewDidLoad() {
      container?.performBackgroundTask { context in 
          // adds 100 dummy records in background
          for i in 1...100 {
             let student = Student(context: context)
             student.name = "student \(i)"
          }
          try? context.save()   // this works because count is printed below
          if let count = try? context.count(for: Student.fetchRequest()) {
               print("Number of students in core data: \(count)")  // prints 100
          }
      }  // end of background inserting.

      // now defining frc:
      if let context = container?.viewContext {
          let request:NSFetchRequest<Student> = Student.fetchRequest()
          request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
          frc = NSFetchedResultsController<Student> (
               fetchRequest: request,
               managedObjectContext: context,
               sectionNameKeyPath: nil,
               cacheName: nil )

          try? frc?.performFetch()   // this works and I get no errors
          tableView.reloadData()
          frc.delegate = self
      }  // end of frc definition
   }
}

如果我使用viewContext 添加一行Studentfrc 将触发所需的方法以在tableView 中显示它。然而,100 个虚拟行未显示。事实上,如果我试图在插入完成后告诉 tableview 重新加载,我的应用程序开始表现得很奇怪并变得有问题,并且没有做它应该做的事情(即:不删除行,不编辑等) .

但是如果我重新启动我的应用程序,而不调用虚拟插入,我可以看到前一次运行插入的 100 行。

唯一的问题是我无法从后台线程调用tableView.reloadData(),所以我尝试这样做:

// after printing the count, I did this:
DispatchQueue.main.async { [weak self] in
    self?.tableView.reloadData()   // causes UI to behave weirdly 
}

然后我尝试调用viewContext.perform 在正确的线程中重新加载表格视图

func viewDidLoad() {

   // code for inserting 100 dummy rows in background thread

   // code for defining frc and setting self as delegate

   if let context = container?.viewContext {
      context.perform { [weak self] in
         self?.tableView.reloadData()    // that also causes UI to behave weirdly

      }
   }
}

如何告诉我的 tableview 以线程安全的方式重新加载和显示 100 个虚拟行?

【问题讨论】:

  • 在执行获取之前尝试设置委托。
  • @JonRose 我试过了。如果我在执行调用之后设置委托,则委托方法不会触发。所以,刷新不会发生,应用程序更容易出错
  • @Ahmad “导致 UI 行为怪异”是什么意思?
  • @staticVoidMan 动画变得迟缓并且表格行上的删除按钮不会触发委托方法commiteditingstyle
  • @Ahmad 您可以创建一个调试存储库并将其添加到您的问题中吗?我认为如果我们可以在调试 repo 上为您提供帮助会更快。

标签: swift uitableview core-data


【解决方案1】:
override func viewDidLoad() {
    super.viewDidLoad()

    //Always need your delegate for the UI to be set before calling the UI's delegate functions.
    frc.delegate = self

    //First we can grab any already stored values.
    goFetch()

    //This chunk just saves. I would consider putting it into a separate function such as "goSave()" and then call that from an event handler.
    container?.performBackgroundTask { context in
        //We are in a different queue than the main queue, hence "backgroundTask".
        for i in 1...100 {
            let student = Student(context: context)
            student.name = "student \(i)"
        }
        try? context.save()   // this works because count is printed below
        if let count = try? context.count(for: Student.fetchRequest()) {
            print("Number of students in core data: \(count)")  // prints 100
        }
        //Now that we are done saving its ok to fetch again.

        goFetch()

    }

    //goFetch(); Your other code was running here would start executing before the backgroundTask is done. bad idea.
    //The reason it works if you restart the app because that data you didn't let finish saving is persisted
    //So the second time Even though its saving another 100 in another queue there were still at least 100 records to fetch at time of fetch.

}


func goFetch() {

    if let context = container?.viewContext {
        let request:NSFetchRequest<Student> = Student.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        frc = NSFetchedResultsController<Student> (
            fetchRequest: request,
            managedObjectContext: context,
            sectionNameKeyPath: nil,
            cacheName: nil )

        try? frc?.performFetch()

        //Now that records are both stored and fetched its safe for our delegate to access the data on the main thread.
        //To me it would make sense to do a tableView reload everytime data is fetched so I placed this inside o `goFetch()`
        DispatchQueue.main.async { [weak self] in
            self?.tableView.reloadData()
        }
    }
}

【讨论】:

    【解决方案2】:

    在大量阅读有关 NSFetchedResultsController 和 NSPersistentContainer 并最终在 SO 找到重要信息后,我想我有一个可行的示例。

    我的代码略有不同,因为我为此使用了一个项目。无论如何,这就是我所做的:

    在我的视图控制器中,我的容器有一个属性

    private var persistentContainer = NSPersistentContainer(name: coreDataModelName)
    

    在 viewDidLoad 中,我加载了持久存储并创建了 100 条记录。

    persistentContainer.loadPersistentStores { persistentStoreDescription, error in
      if let error = error {
        print("Unable to add Persistent Store [\(error)][\(error.localizedDescription)]")
      } else {
        self.createFakeNotes() // Here 100 elements get created
        DispatchQueue.main.async {
          self.setupView() // other stuff, not relevant
          self.fetchNotes() // fetch using fetch result controller
          self.tableView.reloadData()
        }
      }
    }
    

    下面是 createFakeNotes(),我在其中使用单独的上下文在后台线程中插入元素,这段代码几乎取自 Apple's Core Data programming guide,但为了更新 UI,我需要将自动MergesChangesFromParent 设置为 true,我发现出this SO answer

    为了方便测试,我也先删除旧笔记。

    private func createFakeNotes() {
      let deleteRequest = NSBatchDeleteRequest(fetchRequest: Note.fetchRequest())
      do {
        try persistentContainer.persistentStoreCoordinator.execute(deleteRequest, with: persistentContainer.viewContext)
      } catch {
        print("Delete error [\(error)]")
        return
      }
    
      let privateContext = persistentContainer.newBackgroundContext()
      privateContext.automaticallyMergesChangesFromParent = true //Important!!!
    
      privateContext.perform {
        let createDate = Date()
        for i in 1...100 {
          let note = Note(context: privateContext)
          note.title = String(format: "Title %2d", i)
          note.contents = "Content"
          note.createdAt = createDate
          note.updatedAt = createDate
        }
        do {
          try privateContext.save()
          do {
            try self.persistentContainer.viewContext.save()
          } catch {
            print("Fail saving main context [\(error.localizedDescription)")
          }
        } catch {
          print("Fail saving private context [\(error.localizedDescription)")
      }
    }
    

    }

    【讨论】:

    • 嗨@joakim,我使用了上述方法,但它使应用程序崩溃。我在收到响应时同时插入和获取数据我的应用程序性能很慢。我们做什么?
    • Joakim,感谢您提供有关 automaticMergesChangesFromParent 的精彩提示
    【解决方案3】:

    您应该通过从 viewwillappear 调用数据来获取数据,然后尝试重新加载您的 tableview。

    override func viewWillAppear(_ animated: Bool) {
        getdata()
        tableView.reloadData()
    }
     func getdata() {
        let context =  (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        do{
        persons = try context.fetch(Person.fetchRequest()) 
        }
        catch {
            print("fetching failed")
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-22
      • 1970-01-01
      • 2018-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-26
      • 2012-10-18
      • 1970-01-01
      相关资源
      最近更新 更多