multithreadingswiftgrand-central-dispatchmutated

Swift - GCD mutable array multiple threads issue "mutated while being enumerated"


I am currently developing a game where spheres are falling from the sky. While collecting spheres you gain points and after a certain amount of points all spheres accelerate to another speed.

  1. New spheres are continuously added to an Array (4 Spheres inside each SKNode).
  2. When they are to accelerate I iterate through the array to increase the speed of all of them.
  3. When the spheres have fallen out of the screen I remove them from the Array.
class GameScene: SKScene, SKPhysicsContactDelegate {
...
var allActiveNodes = Array<SKNode>()
private let concurrentNodesQueue = dispatch_queue_create(
    "com.SphereHunt.allActiveNodesQueue", DISPATCH_QUEUE_CONCURRENT)
...

//1. This is where the new spheres are added to the Array via a new thread
func addSpheres(leftSphere: Sphere, middleLeftSphere: Sphere, middleRightSphere: Sphere, rightSphere: Sphere){
...
dispatch_barrier_async(self.concurrentNodesQueue){
        self.allActiveNodes.append(containerNode)
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        dispatch_async(queue) {
//Set the new spheres in motion
            self.runPastAvatar(containerNode)
        }
    }

//2. This function starts a thread that will increase the speed of all active spheres
    func increaseSpeed20percent(){
        durationPercentage = durationPercentage * 0.8
        dispatch_sync(self.concurrentNodesQueue){
        let copyAllActiveNodes = self.allActiveNodes
        let count = copyAllActiveNodes.count

        for index in 0...count-1{
            let node = copyAllActiveNodes[index]
            node.removeAllActions()
            self.runPastAvatar(node)
        }
    }
}
//3. This method removes the sphere that is not in screen anymore from the Array
    func removeLastNode(node: SKNode){
    dispatch_barrier_async(self.concurrentNodesQueue){
            self.allActiveNodes.removeAtIndex(0)
            node.removeFromParent()
            println("Removed")
            }
    }

I am not sure if I have understood GCD correctly, I have tried multiple solutions and this is the one I was sure was going to work. I always end up with the same error message:

*** Terminating app due to uncaught exception 'NSGenericException', 
reason: '*** Collection <__NSArrayM: 0x17004c9f0> was mutated while being enumerated.'

How do I get the threads to not interfere with each other while handling the array?


Solution

  • My problem was that I was using a thread-sleep solution to fire new spheres in a time interval. This was a bad choice but should not have produced such an error message in my opinion. I solved it using NSTimer to fire new spheres in a time interval. This gave the game a bit of a lag, but it is more robust and won't crash. Next up is finding out how to use the NSTimer without creating such a lag in the game!