My app uses a tableView with a data source that can be updated asynchronously by multiple threads.
When the data source is changed, the tableView is updated, not reloaded, using
tableView.performBatchUpdates({
tableView.deleteRows(at: deletions, with: .automatic)
tableView.insertRows(at: insertions, with: .automatic)
for (from, to) in movements {
tableView.moveRow(at: from, to: to)
}
}, completion: { (finished) in
if !finished {
self.tableView.reloadData()
} else {
// some cleanup code
}
completion(finished)
}
where completion(finished) is some completion block.
Typically, the tableView update takes 0.25 sec. During this time, the data source must not be changed. What is the best method to ensure this?
I can imagine that I acquire an NSCondition before tableView.performBatchUpdates, and release it in the completion block, of course also for every other read and write access to the data source, as suggested here. But most posts on SO suggest not to use such low-level sync mechanism, but use GCD instead.
One suggestion is to use a thread-safe SynchronizedArray using a concurrent queue with sync reads and async barrier writes, which allows concurrent reads. But I don’t know how I could lock out writes during tableView.performBatchUpdates when I use GCD.
Is there a standard solution to this problem?
EDIT:
I was not happy with the solution I provided below, since it does not allow concurrent reads.
Thus I came up with the following better solution, a
WriteLockableSynchronizedArray.It is based much closer on Basem Emara's SynchronizedArray (thanks again, Basem), i.e. it does allow concurrent reads, and has the following features:
WriteLockableSynchronizedArrayfrom anArray, and a readonly propertyarraythat returns the underlying array.lockArray()andunlockArray()that can be called before and after batch tableView operations.Sequenceprotocol, so that aWriteLockableSynchronizedArraycan be used e.g. in statements likefor element in writeLockableSynchronizedArray {}.WriteLockableSynchronizedArrayis write locked, all writes are delayed until it is unlocked again. After unlocking, all delayed writes are executed in order.Here is the new solution (recommended):
Here is my previous solution (no longer recommended):
LockableArray, a modification of Basem Emara's (thanks!)SynchronizedArraythat uses a recursive lock to synchronize accesses to the array.LockableArrayfrom anArray, and a readonly property that returns the underlyingArray.lockArray()andunlockArray()that can be called before and after batch tableView operations.Sequenceprotocol, so that aLockableArraycan be used e.g. in statements likefor element in lockablaArray {}.A disadvantage of this solution is that multiple reads can not be executed concurrently as in Basem Emara's
SynchronizedArray.Here is the implementation: