代码之家  ›  专栏  ›  技术社区  ›  Adrian rohit chauhan

FRC内容更改时UITableView异常

  •  5
  • Adrian rohit chauhan  · 技术社区  · 6 年前

    UITableView 它显示来自 NSFetchedResultsController . FRC中显示的数据量各不相同,从一次提取约20条记录到最大一次提取约14k条记录。我遇到的问题是,如果在tableView在大的fetch上滚动时执行fetch,我会得到一个异常。到…的时候 cellForRowAtIndexPath

    我发现 this post ,这听起来像是我遇到的,虽然我无法通过这种方法解决它。

      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseID, for: indexPath) as! CustomTableViewCell
        // Exception occurs here
        let myObject = fetchedResultsController.object(at: indexPath)
        cell.myObject = myObject
    
        return cell
      }
    

    0   CoreFoundation                      0x0000000110b241e6 __exceptionPreprocess + 294
    1   libobjc.A.dylib                     0x000000010b820031 objc_exception_throw + 48
    2   CoreData                            0x000000010c2458fd -[NSFetchedResultsController objectAtIndexPath:] + 685
    3   MyApp                         0x000000010aaf36c5 _T011MyApp0B14ViewControllerC05tableC0So07UITableC4CellCSo0fC0C_10Foundation9IndexPathV12cellForRowAttF + 437
    4   MyApp                         0x000000010aaf39ec _T011MyApp0B14ViewControllerC05tableC0So07UITableC4CellCSo0fC0C_10Foundation9IndexPathV12cellForRowAttFTo + 92
    5   UIKit                               0x000000010cf45567 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 783
    6   UIKit                               0x000000010cf45ae4 -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 74
    7   UIKit                               0x000000010cf0ceaa -[UITableView _updateVisibleCellsNow:isRecursive:] + 3168
    8   UIKit                               0x000000010cf2d7e0 -[UITableView layoutSubviews] + 176
    9   UIKit                               0x000000010ceb77a8 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1515
    10  QuartzCore                          0x000000010cc21456 -[CALayer layoutSublayers] + 177
    11  QuartzCore                          0x000000010cc25667 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 395
    12  QuartzCore                          0x000000010cbac0fb _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 343
    13  QuartzCore                          0x000000010cbd979c _ZN2CA11Transaction6commitEv + 568
    14  UIKit                               0x000000010cde22ef _UIApplicationFlushRunLoopCATransactionIfTooLate + 167
    15  UIKit                               0x000000010d747662 __handleEventQueueInternal + 6875
    16  CoreFoundation                      0x0000000110ac6bb1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    17  CoreFoundation                      0x0000000110aab4af __CFRunLoopDoSources0 + 271
    18  CoreFoundation                      0x0000000110aaaa6f __CFRunLoopRun + 1263
    19  CoreFoundation                      0x0000000110aaa30b CFRunLoopRunSpecific + 635
    20  GraphicsServices                    0x0000000115c2fa73 GSEventRunModal + 62
    21  UIKit                               0x000000010cde8057 UIApplicationMain + 159
    22  MyApp                         0x000000010aacd427 main + 55
    23  libdyld.dylib                       0x0000000111c5b955 start + 1
    

    我欢迎解决这一问题的建议。我想避免做一些像在 tableview 减速完成。谢谢你的阅读。

    更新

    作为对一个评论的回应,下面是我对 UIFetchedResultsControllerDelegate documentation

    extension ViewController: NSFetchedResultsControllerDelegate {
      func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
      }
    
      func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
                      didChange sectionInfo: NSFetchedResultsSectionInfo,
                      atSectionIndex sectionIndex: Int,
                      for type: NSFetchedResultsChangeType) {
        switch type {
        case .insert:
          tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
        case .delete:
          tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
        case .move:
          break
        case .update:
          break
        }
      }
    
      func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
                      didChange anObject: Any,
                      at indexPath: IndexPath?,
                      for type: NSFetchedResultsChangeType,
                      newIndexPath: IndexPath?) {
        switch type {
        case .insert:
          tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
          tableView.deleteRows(at: [indexPath!], with: .fade)
        case .update:
          tableView.reloadRows(at: [indexPath!], with: .fade)
        case .move:
          tableView.moveRow(at: indexPath!, to: newIndexPath!)
        }
      }
    
      func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
      }
    }
    

    新的尝试

    NSFetchedResultsControllerDelegate guard 我关掉了动画。我在同一个地方做同一件事也会遇到同样的车祸。

      func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
                      didChange anObject: Any,
                      at indexPath: IndexPath?,
                      for type: NSFetchedResultsChangeType,
                      newIndexPath: IndexPath?) {
    
        guard let indexPath = indexPath else { return }
    
        switch type {
        case .insert:
          guard let newIndexPath = newIndexPath else { return }
          tableView.insertRows(at: [newIndexPath], with: .none)
        case .delete:
          tableView.deleteRows(at: [indexPath], with: .none)
        case .update:
          tableView.reloadRows(at: [indexPath], with: .none)
        case .move:
          guard let newIndexPath = newIndexPath else { return }
          tableView.moveRow(at: indexPath, to: newIndexPath)
        }
      }
    

    更新2

    作为对注释的响应,这是我用来更新谓词的代码。我有一个观察者 startDate endDate 这叫 updatePredicate() 开始日期 实际结束 segmentedControl 按下:

      // This is used to set values for searching via segmentedControl
      @objc dynamic var startDate: Date?
      @objc dynamic var endDate: Date?
    
      func updatePredicate() {
        // you need a start date or stop executing
        guard let startDate = startDate else { return }
    
        var predicateArray: [NSPredicate] = []
    
        if let endDate = endDate {
          let startEndPredicate = NSPredicate(format: "time >= %@ AND time <= %@", argumentArray: [startDate, endDate])
          predicateArray.append(startEndPredicate)
        } else {
          // use the startDate's end of day if endDate is nil
          let startPredicate = NSPredicate(format: "time >= %@ AND time <= %@", argumentArray: [startDate, startDate.endOfDay!])
          predicateArray.append(startPredicate)
        }
    
        let sliderPredicate = NSPredicate(format: "magnitude >= %f", argumentArray: [magSlider.value])
        predicateArray.append(sliderPredicate)
    
        currentPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicateArray)
    
        fetchResults()
      }
    

    下面是fetchResults():

      func fetchResults() {
        do {
          fetchedResultsController.fetchRequest.fetchBatchSize = 35
          fetchedResultsController.fetchRequest.fetchLimit = 1_000
          try fetchedResultsController.performFetch()
        } catch let error {
          print("\(error) \(error.localizedDescription)")
        }
        DispatchQueue.main.async {
          self.tableView.reloadData()
          self.tableView.setContentOffset(.zero, animated: true)
          self.updateLabelsForResults()
        }
      }
    

    更新3

    针对另一项评论,以下是FRC声明:

      private lazy var fetchedResultsController: NSFetchedResultsController<EarthquakeEntity> = {
        let managedObjectContext = appDelegate.persistentContainer.viewContext
        let fetchRequest: NSFetchRequest<EarthquakeEntity> = EarthquakeEntity.fetchRequest()
        let dateSortDescriptor = NSSortDescriptor(key: #keyPath(EarthquakeEntity.time), ascending: false)
        fetchRequest.sortDescriptors = [dateSortDescriptor]
        let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: frcKeyPath, cacheName: nil)
        frc.delegate = self
    
        return frc
      }()
    
    2 回复  |  直到 6 年前
        1
  •  2
  •   Adrian rohit chauhan    6 年前

    我最后和他玩了 fetchLimit 这似乎解决了我的问题。

    执行提取时,我按如下方式使用它:

    fetchedResultsController.fetchRequest.fetchLimit = 2_500
    

    2500是我在不破坏应用程序的情况下所能得到的最大值。

        2
  •  0
  •   Eugene Dudnyk    6 年前

    当FRC与 fetchBatchSize

    代理数组的线程安全性与对其进行操作的托管对象的线程安全性相同—必须从底层MOC的专用队列访问它。您可以在本文后面检查线程安全是否是问题: https://oleb.net/blog/2014/06/core-data-concurrency-debugging/ . 理想情况下,访问必须采用以下方式:

    var myObject;
    fetchedResultsController.managedObjectContext.performBlockAndWait({
        myObject = fetchedResultsController.object(at: indexPath) 
    })
    cell.myObject = myObject
    

    此外,在坠机时,请检查这些陈述是否属实:

    1) fetchedResultsController.sections.first.numberOfObjects <= 
                                             fetchedResultsController.fetchRequest.fetchLimit
    
    2) fetchedResultsController.sections.first.numberOfObjects == 
                                             tableView.numberOfRows(inSection: 0)
    

    如果1)不是真的- fetchLimit 不受财务报告委员会的尊重 获取批大小

    如果2)不是真的-这意味着 tableView