1

In application I am working on, I have to take one element of the array by specifying element's index in the array, like this array[index]. It's a simple UICollectionView which is populated with items I am getting from the array.

In order to guard against Index out of range exception, I am doing this:

guard index < array.count else { return }
return array[index]

Even though I have this guard, I got an Index out of range exception on the array[index] line (but not always).

I don't know how this can happen.

I have even added another check:

extension Collection where Indices.Iterator.Element == Index {
    subscript (optional index: Index) -> Iterator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

So I am doing this (this is the actual code snippet from the application):

...
guard let section = chatSections[optional: indexPath.section] else {
    return nil
}
guard indexPath.item < section.itemViewModels.count else {
    return nil
}
return section.itemViewModels[optional: indexPath.item]

It doesn't always happen, but sometimes I get the Index out of Range exception there.

Exception

I was debugging most of the day, trying to figure out conditions when crash happens so I might be able to figure out why, but it seems to happen randomly.

Does anyone have any idea how is this possible? Did anyone encounter this kind of issue?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
JPetric
  • 3,838
  • 28
  • 26

2 Answers2

1

Are you, by any chance, doing any of your updating from a background thread/queue? If so, make sure your UI interactions are done on the main thread/queue and that you aren't changing the array contents behind your UI's back.

That is, if you're changing the array contents in one queue and trying to update your UI while this is happening, your guard statement could be passing just before the array is modified elsewhere, then by the time the rest of your UI-interacting code executes, the index my no longer be valid.

Without a more complete picture, it's hard to say what's going on, but all these bounds checks you're adding in order to guard against mysteriously-changing array indexes are a big clue to multithreading shenanigans.

Joshua Nozzi
  • 60,946
  • 14
  • 140
  • 135
-2
  • I believe you have multiple access to the your data source array (multiple thread try to access add/remove from the array).
  • to overcome this you should use something to enforce synchronization while accessing your array, there is multiple approce using semaphore or DispatchGroup.

I would recommend to use semaphore since the array is considered as shared resource, example:

    private let semaphore = DispatchSemaphore(value: 1)
    private var _array:[Item] = []
    var array:[Item] {
        get {
            semaphore.wait()
            let result = self._allMessages
            defer {
                semaphore.signal()
            } 
            return result
        }
        set {
            semaphore.wait()
            self. _array = newValue
            semaphore.signal()
        }
    }

and use the array variable to access the array data source not the private _array.

  • Though not the only approach to solving this issue, it's better to use dispatch barriers for enforcement at array level as noted in answers here: https://stackoverflow.com/questions/28191079/create-thread-safe-array-in-swift – Joshua Nozzi Jun 14 '19 at 16:49
  • It's cool I said there are many ways to solve the synchronization and thread safety and this is was an example. Thanks for noting out other ways. and I am not sure why the answer is getting down voted for. – Hossam Sherif Jun 14 '19 at 17:13
  • I personally downvoted your answer because it's far more complicated than simply using dispatch barriers. Don't quote me on this, but I believe this is also less performant if you have a lot of reading and writing happening at the same time, which is why Apple themselves push the barrier approach for a variety of things. Even saving: (see advice at 09:14 of the *Advances in Foundation* WWDC 2019 video). – Joshua Nozzi Jun 14 '19 at 17:34
  • as I said this is an example and I stated the possible problem and some possible solutions, and also it has information that could help other explore other solutions for synchronization and thread safety. So I don't think you were fair here with the down vote. – Hossam Sherif Jun 14 '19 at 17:44
  • (shrugs) Sorry. I don't think it's a good solution for the reasons I stated. Obviously at least one other person felt the same as of this writing. I'm not going to argue about it. – Joshua Nozzi Jun 14 '19 at 17:49