【问题标题】:Invalid update on TableViewTableView 上的无效更新
【发布时间】:2016-05-28 06:27:59
【问题描述】:

StackOverflow 的朋友们好。

我的应用程序上有一个聊天屏幕,它根据数组的实际大小执行插入和删除。看这个:

func addObject(object: Object?) {

    if comments == nil || object == nil || object?.something == nil || object?.anything == nil {
      return
    }

    self.objectsTableView.beginUpdates()

    if self.objects!.count == 10 {
      self.objects?.removeAtIndex(9)
      self.objectsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow : 9, inSection: 0)], withRowAnimation: .Right)
    }

    self.objects?.insert(object!, atIndex: 0)
    self.objectsTableView.insertRowsAtIndexPaths([NSIndexPath(forRow : 0, inSection: 0)], withRowAnimation: .Right)

    self.objectsTableView.endUpdates()

  }

但是经过一些压力测试后,日志通知:

无效更新:第 0 节中的行数无效。 更新 (1) 后包含在现有节中的行必须是 等于该节之前包含的行数 update (10),加上或减去插入或删除的行数 该部分(插入 1 个,删除 0 个)并加上或减去数量 移入或移出该部分的行(0 移入,0 移出)。

我不知道发生了什么,只有在插入对象非常极端时才会发生这种情况,例如每 0.2 秒插入一个。

有人知道我能做到吗?

【问题讨论】:

  • 您是否在多个线程中更新self.objects 数组? Swift 数组不是线程安全的,所以你应该采取适当的预防措施来预防并发更新
  • 所以是的,你是。您需要保护您对阵列的更新 - 请参阅此处的 Matt Bridges 答案 - stackoverflow.com/questions/24045895/…(不是公认的答案)
  • 不工作。在 Array 的 145 项上崩溃。
  • 好吧,您问题中的原始崩溃消息表明该数组是空的(嗯,它有一个项目),而它预期有 10 个,所以看看你可能在哪里更新数组

标签: ios uitableview tableviewcell


【解决方案1】:

解决问题的人的姓名:Semaphore

错误仍然发生,但仅在列表中的项目大小较大时发生。我不知道会是什么。

数据源协议:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let count = self.objects?.count ?? 0
    if self.semaphore != nil && semaphoreCode == BLOCKED_STATE {
      dispatch_semaphore_signal(self.semaphore!)
    }
    return count
  }

添加对象的方法:

func addObject(object: Object?) {

    if object == nil {
      return
    }

    if self.semaphore != nil {
      let tempSemaCode = dispatch_semaphore_wait(semaphore!, 100000)
      if tempSemaCode == BLOCKED_STATE {
        self.semaphoreCode = RELEASED_STATE
      }
    }

    if self.objects != nil && semaphoreCode != BLOCKED_STATE {

      var needsRemoveLastItem = false
      if self.objects!.count == 10 {
        self.objects?.removeAtIndex(9)
        needsRemoveLastItem = true
      }
      self.objects?.insert(object!, atIndex: 0)

      if self.objects!.count > 0 {
        self.objectsTableView.beginUpdates()
        if needsRemoveLastItem {
          self.objectsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow : 9, inSection: 0)], withRowAnimation: .Right)
        }
        self.objectsTableView.insertRowsAtIndexPaths([NSIndexPath(forRow : 0, inSection: 0)], withRowAnimation: .Right)
        self.objectsTableView.endUpdates()
        self.semaphore = dispatch_semaphore_create(BLOCKED_STATE)
      }

    }

  }

【讨论】:

    【解决方案2】:

    模型不匹配

    更新后现有节中包含的行数 (1) 必须等于更新前该节中包含的行数 (10),加上或减去从该节中插入或删除的行数(插入 1 个,删除 0 个)

    对于通情达理的人来说,UITableView 认为您应该有 11 行:
    更新前的10 + 1 插入。

    更新后现有部分中包含的行数 (1)

    ...指numberOfRowsInSection 正在为0 部分返回1,这表明objects 数组不同步,假设您使用如下所示:

    override func tableView(tableView: UITableView,
                            numberOfRowsInSection section: Int) -> Int {
        return objects.count
    }
    

    使用NSFetchedResultsController

    一个干净的解决方案是使用NSFetchedResultsController 作为模型和 UI 之间的接口。它对样板代码进行了深入研究,是确保线程安全的绝佳平台。 Documentation here.


    注意:

    效果不错!单元格似乎旋转到顶部。
    我无法使用您制作的Gist 破坏它,也无法安排多个并发测试。您的Object 数组必须有恶意访问

    演示

    这个简化版本有效。只需将doPlusAction 挂接到按钮操作并观看它循环:

    class TableViewController: UITableViewController {
        var objects:[Int] = [0,1,2,3,4]
        var insertions = 5
    
        @IBAction func doPlusAction(sender: AnyObject) {
            tableView.beginUpdates()
    
            objects.removeAtIndex(4)
            tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .Right)
            objects.insert(insertions++, atIndex: 0)
            tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Right)
            tableView.endUpdates()
    
            let delay = 0.1 * Double(NSEC_PER_SEC) //happens the same with this too, when reach 100-150 items
            let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
            dispatch_after(time, dispatch_get_main_queue()) { () -> Void in
                self.doPlusAction(self)
            }
        }
    
        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return objects.count
        }
    
        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
            cell.textLabel!.text = "Cell \(objects[indexPath.row])"
            return cell
        }
    }
    

    【讨论】:

    • 我尝试添加dispatch_semaphore_createNSBlockOperation。也不工作。
    • 看看我的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多