Jake
Jake

Reputation: 13753

Int in swift closure not incrementing

I have a closure in the code below that executes over an array of custom SKShapeNodes (blockNode). The problem I'm having is that completedActionsCount is always 1, no matter how many how many times the closure is executed. Is completedActionsCount just copied at the start of the closure?? Is there something I'm missing here?

for action in blockActionSets[index]
        {
            var blockActionSet = blockActionSets[index]

            var completedActionsCount = 0

            let blockNode = getBlockNodeForBlock(action.block)

            if blockNode
            {
                blockNode!.updateWithAction(action, completion:
                    { blockAction in

                        completedActionsCount++

                        println(completedActionsCount)
                        println(blockActionSet.count)
                        if (completedActionsCount == blockActionSet.count)
                        {
                            index = index + 1
                            self.executeBlockActionSetAtIndexRecursive(index, blockActionSets: blockActionSets, completion: completion)
                        }
                    })
            }
            else
            {
                completedActionsCount++
            }
        }

The following code is how the block is executed:

func updateWithAction(blockAction : BlockAction, completion : (BlockAction) -> Void)
{
    if blockAction.blockActionType == BlockActionType.ChangeColor
    {
        var startColor = CIColor(CGColor: self.fillColor.CGColor)
        var endColor = CIColor(CGColor: UI.getUIColorForBlockType(blockAction.endBlockType).CGColor)

        var startRed = startColor.red()
        var startGreen = startColor.green()
        var startBlue = startColor.blue()
        var endRed = endColor.red()
        var endGreen = endColor.green()
        var endBlue = endColor.blue()

        var action = SKAction.customActionWithDuration(CHANGE_COLOR_ANIMATION_DURATION, actionBlock: {(node : SKNode!, elapsedTime : CGFloat)->Void in
                var blockNode = node as? BlockNode

                if blockNode
                {
                    var ratio : CGFloat = min(1.0, elapsedTime / CGFloat(self.CHANGE_COLOR_ANIMATION_DURATION))

                    blockNode!.fillColor = UIColor(red: startRed + ratio * (endRed - startRed),
                        green: startGreen + ratio * (endGreen - startGreen),
                        blue: startBlue + ratio * (endBlue - startBlue),
                        alpha: 1.0)
                }

                if elapsedTime >= CGFloat(self.CHANGE_COLOR_ANIMATION_DURATION)
                {
                    completion(blockAction)
                }
            })

        self.runAction(action)
    }
}

Upvotes: 1

Views: 527

Answers (1)

Jiaaro
Jiaaro

Reputation: 76918

I believe the issue is that you are defining the completedActionsCount variable inside the loop, so each time through the loop a new Int value is defined and the value is reset to 0

does this work for you?

var completedActionsCount = 0

for action in blockActionSets[index] {
    var blockActionSet = blockActionSets[index]

    …
}

re-reading the question… I'm not sure if you intended for each blockActionSet to have it's own counter. But, to answer one of your sub-questions, I'm pretty sure that even though Int is a value type, it is not being copied into the closure, it's available via the outer scope, and you should be able to modify it.

Is updateWithAction:completion: run in the current thread? if it's asynchronous, that may be a factor.

response to update: Generally any API that has "withDuration" in the name will be async to avoid blocking the main thread.

I'm not sure exactly what is happening, but if the counter is being copied because it's a value type I don't know if switching to the main thread will actually fix your issue, but it's worth a try:

blockNode!.updateWithAction(action, completion:
    { blockAction in
        dispatch_async(dispatch_get_main_queue()) {            
            completedActionsCount++
            …
        }
    }
})

I have somewhat low hopes for this approach though since I suspect the value is being copied and copying it again is certainly not going to give you back the original reference. You may be able to wrap the reference to the counter in a reference type (like a variable array or dictionary):

var completedActionsCounts : [Int:Int] = [:]

for action in blockActionSets[index] {
    var blockActionSet = blockActionSets[index]

    completedActionsCounts[index] = 0
    …

    blockNode!.updateWithAction(action, completion:
        { blockAction in
            dispatch_async(dispatch_get_main_queue()) {            
                completedActionsCounts[index] += 1
                …
            }
        }
    })
}

Upvotes: 2

Related Questions