I have a for loop that I want to add a delay between iterations. I have changed waitUntilDone to YES and get the same results. My array only has two numbers in it and both are called after the five seconds instead of:
0s - nothing 5s - Block called 10s- Block called
for(NSNumber* transaction in gainsArray) {
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSLog(@"Block");
[self performSelectorOnMainThread:@selector(private_addTransactionToBankroll:)
withObject:transaction waitUntilDone:NO];
});
}
2015-06-16 20:11:06.485 TestApp[97027:6251126] Block
2015-06-16 20:11:06.485 TestApp[97027:6251127] Block
I am using Cocos2d if that matters
If you don't wish to use delay function then you can use loops to produce delay in a C program. We have not written any statement in the loop body. You may write some statements that doesn't affect logic of the program.
C programming code for delay. This C program exits in ten seconds, after the printf function is executed the program waits for 10000 milliseconds or 10 seconds and then it terminates.
The biggest problem with using a for-loop to do this is that you are wasting CPU power. When using sleep, the CPU can, in a sense, take a break (hence the name "sleep") from executing your program. This means that the CPU will be able to run other programs that have meaningful work to do while your program waits.
But in the for-loop the CPU continuously have to do work to increase a variable. For what purpose? Nothing. But the CPU doesn't know that. It's told to increase the variable, so that's what it will do.
The for loop will dispatch one right after the other so they will essentially delay for the same time.
Instead set a different increasing delay for each:
double delayInSeconds = 0.0;
for(NSNumber* transaction in gainsArray)
{
delayInSeconds += 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
NSLog(@"Block");
[self performSelectorOnMainThread:@selector(private_addTransactionToBankroll:)
withObject:transaction
waitUntilDone:NO];
});
}
@zaph has a pretty good solution. I thought I'd try from a different angle. Since Objective-C is Objective-C, why not define some kind of object to do this timed looping? Hint: this exists. We can use NSTimer and its userInfo property to work this out. I think the solution is sort of elegant, if not a nasty hack.
// Somewhere in code.... to start the 'loop'
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
action:@selector(processNextTransaction:)
userInfo:@{
@"gains": [gainsArray mutableCopy]
}
repeats:NO];
// What handles each 'iteration' of your 'loop'
- (void)processNextTransaction:(NSTimer *)loopTimer {
NSMutableArray *gains = [loopTimer.userInfo objectForKey:@"gains"];
if(gains && gains.count > 0) {
id transaction = [gains firstObject];
[gains removeObjectAtIndex:0]; // NSMutableArray should really return the object we're removing, but it doesn't...
[self private_addTransactionToBankroll:transaction];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
action:@selector(processNextTransaction:)
userInfo:@{
@"gains": gains
}
repeats:NO];
}
}
I would check that the NSTimer is retained by being added to the run-loop. If that's not the case, you should store a reference to it as a property on whatever class is managing all of this.
It's also worth noting that because NSTimers get installed on the main run loop by default, you don't need to worry about all the GCD stuff. Then again, if this work is pretty difficult work, you may want -processNextTransaction:
to offload its work onto another GCD queue and then come back to the main queue to initialize the NSTimer instance.
Be sure to use the -scheduledTimer...
method; timer...
class methods on NSTimer
don't install it on any loop, and the objects just sit in space doing nothing. Don't do repeats:YES
, that would be tragic, as you'd have timers attached to the run loop willy-nilly, with no references pointing to them to know how or where to stop them. This is generally a bad thing.
To avoid EXC_BAD_ACCESS
exceptions, never dealloc the object whose method an NSTimer
is going to call, if that timer hasn't fired yet. You may want to store the pending NSTimer
in a property on your class so that you can handle this sort of thing. If it's a ViewController that is managing all this (which it typically is), then I would use the following code to clean up the timer on -viewWillDisappear
. (This assumes that you're setting a new timer to some @property
, self.timer
)
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if(self.timer) {
[self.timer invalidate]; // -invalidate removes it from the run loop.
self.timer = nil; // Stop pointing at it so ARC destroys it.
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With