【问题标题】:Problems with asynchronous data, UITableView, and reloadRowsAt异步数据、UITableView、reloadRowsAt的问题
【发布时间】:2018-02-14 19:20:43
【问题描述】:

我正在尝试实现一个 tableView,它有 4 个不同的可能原型单元格。它们都继承自 UITableViewCell 基类并实现其协议。

对于其中两个单元格,有异步数据获取,但特别是其中一个单元格让我很适应。流程如下:

1) 出列可重用单元

2) 调用配置

func configure(someArguments: ) {
   //some checks
   process(withArguments: ) { [weak self in] in
      if let weakSelf = self {
         weakSelf.reloadDelegate.reload(forID: id)
      }
   }
}

3) 如果异步数据在缓存中,请使用可用的图像/数据/资料配置单元并开心

4) 如果异步数据不在缓存中,获取它,缓存它,然后调用完成

func process(withArguments: completion:) {
    if let async_data = cache.exists(forID: async_data.id) {
        //set labels, add views, etc
    } else { 
       fetch_async_data() {
          //add to cache
          //call completion
       }
     } 
 }

5) 如果调用了完成,则通过将索引路径向上传递到UITableViewController 并调用reloadRows(at:with:) 来重新加载有问题的行

 func reload(forID: ) {
   tableView.beginUpdates()
   tableView.reloadRows(at: indexPath_matching_forID with: .automatic)
   tableView.endUpdates()
 }

现在,我的理解是 reloadRows(at:with:) 将触发另一个 dataSource/delegate 循环,从而导致一个新的可重复单元被出列,并再次调用 configure 方法,从而使步骤 #3 快乐(异步数据现在将在缓存中,因为我们刚刚获取它)。

除了……这并不总是发生。如果我的初始提取中有需要重新加载的单元格,它会起作用 - 他们获取数据并显示它。但是,有时,向下滚动到另一个需要获取的单元格不会获得正确的数据......或者更具体地说,它不会触发使用正确数据填充单元格的重新加载。我可以看到缓存正在使用新数据进行更新,但它没有...显示出来。

但是,如果我完全滚动过坏单元格,然后向上滚动,则使用正确的数据。那么,到底是什么 reloadRows?!

我尝试在 DispatchQueue.main.async 中包装各种东西,但无济于事。

reloadData 可以正常工作,但价格昂贵,因为在完全重新加载时可能会触发许多异步请求(此外,当单元格返回时,它会导致一些过度闪烁)

任何帮助将不胜感激!

【问题讨论】:

  • 当“它不会触发使用正确数据填充单元格的重新加载”时,您在滚动到的单元格中究竟看到了什么?它是否显示来自另一个索引路径的一些陈旧内容?还是只是空白?
  • 来自另一个索引路径的陈旧内容 - 因此,如果在第一批中有一个具有相同重用类型的单元格,它将在后面的单元格中显示该单元格的数据,不会重新填充。同样,我知道缓存已正确填充(是的打印语句),但 process(withArguments:) 方法再也不会被调用,因此它无法填写正确的数据。
  • 在这种情况下,我会在indexPath_matching_forID 中查看您的逻辑。可能是针对不正确的索引路径进行了重新加载。如何将 id 关联到新的索引路径?
  • 在 cellForRow 中,在调用 configure 之前,我将其添加到字典 idToIndexPath[id] = indexPath 并使用 dataArray[indexPath.row] 获取 id(它返回一个带有 id 的对象)
  • 打印出正在重新加载的 indexPath 后,我可以直观地确认正在使用“正确”的 indexPath。

标签: ios swift uitableview reloaddata reloadrows


【解决方案1】:

重复使用的单元格不是“新鲜的”。等待内容时清除单元格。

func process(withArguments: completion:) {
    if let async_data = cache.exists(forID: async_data.id) {
        //set labels, add views, etc
    } else { 
       fetch_async_data() {
          // ** reset the content of the cell, clear labels etc ** 
          //add to cache
          //call completion
       }
    } 
 }

【讨论】:

  • 我之前在 if it exists in the cache configure stuff... 块中进行了重置,因为理论上如果调用发生,那么我可以在使用看到任何内容之前覆盖数据。将我必须做的小重置移动到异步块显示相同的结果,除了现在根本没有显示数据。随后对 process() 的调用只是没有发生
  • 在缓存未命中的情况下,我不清楚何时/如何重置重用单元格上的 ID。完成处理程序中的“id”来自哪里?您的完成处理程序是否有可能引用重用单元格中的旧 id?如果是这样,您的 reload 函数可能会将旧 id 转换为不可见的 indexPath,因此 reloadRows 调用将什么也不做(如您所述)。这结合了我上面指出的内容(处理重用)和@LeonidUsov 提到的内容(你的 id 到 indexPath 转换的逻辑)。
  • 理论上这不应该是一个问题 - 第一次通过 cellForRow 每行从我的反序列化 JSON 中获取一个唯一的数据位,然后获取该 JSON 的 id 并在字典出来了。当我重新加载单元格时,它使用关联 JSON 数据的 id 从我在 tableViewcontroller 级别创建的字典中获取索引路径
  • 我还乱扔了print 语句,并观察到当我在模拟器中向下滚动时,正确的 indexPath 被传递给 reloadRows。
  • 是的,我阅读了您关于 idToIndexPath 字典的 cmets。但是完成处理程序中的“id”是什么? weakSelf.reloadDelegate.reload(forID: id)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-05
  • 2015-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多