3

Hi friends of StackOverflow.

I have a chat screen on my app and I it perform a insertion and deletion based on the actual size of the an Array. Look this:

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()

  }

But after some stress test, the log notify:

Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

I don't know whats happening, this happens only when the insert of objects is very extreme, like one per 0.2 seconds.

Someone know that I can do?

Pedro Paulo Amorim
  • 1,838
  • 2
  • 27
  • 50
  • 2
    Are you updating the `self.objects` array in multiple threads? Swift arrays are not thread safe, so you should take appropriate precautions to precent concurrent update – Paulw11 Feb 16 '16 at 19:48
  • I'm using this: https://gist.github.com/ppamorim/845517fed6bec0fd41ff – Pedro Paulo Amorim Feb 16 '16 at 19:57
  • So yes, you are. You need to guard your updates to the array - see Matt Bridges answer here - http://stackoverflow.com/questions/24045895/what-is-the-swift-equivalent-to-objective-cs-synchronized (Not the accepted answer) – Paulw11 Feb 16 '16 at 20:01
  • Not working. Crash on 145 item of the Array. – Pedro Paulo Amorim Feb 16 '16 at 20:18
  • Well, the original crash message in your question indicates that the array was empty (well, it had one item) when it was expected to have 10, so look where else you may be updating the array – Paulw11 Feb 16 '16 at 20:19

2 Answers2

1

Model mismatch

The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted)

In plain English for the reasonable man, the UITableView thinks you should have 11 rows:
10 before the update + 1 inserted.

number of rows contained in an existing section after the update (1)

...refers to numberOfRowsInSection is returning 1 for section 0, which indicates that the objects array is out of sync, assuming you use something like below:

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

Use NSFetchedResultsController

A clean solution is to use NSFetchedResultsController to be the interface between your model and the UI. It has well studied boilerplate code and is a great platform to ensure thread safety. Documentation here.


Note:

Neat effect! The cell seems to rotate around to the top.
I could not break it using the Gist you produced, nor scheduling multiple concurrent tests. There must be a rogue access to your Object array.

Demo

This simplified version works. Just hook doPlusAction to a button action and watch it loop:

neat effect

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
    }
}

SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
0

Name of the guy that solved the problem: Semaphore

The error still happens, but only with a high size of items on list. I don't know what can be.

The DataSource protocol:

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
  }

The method that add object:

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)
      }

    }

  }
Pedro Paulo Amorim
  • 1,838
  • 2
  • 27
  • 50