Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective C for loop delay

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

like image 918
Asdrubal Avatar asked Jun 17 '15 00:06

Asdrubal


People also ask

How to produce delay in a C program without using function?

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.

What is the delay in C programming for printf?

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.

What are the disadvantages of using a for-loop?

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.

Why does the CPU need to increase a variable in for-loop?

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.


Video Answer


2 Answers

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];

                });
}
like image 104
zaph Avatar answered Sep 21 '22 06:09

zaph


@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.
    }
}
like image 40
josefdlange Avatar answered Sep 19 '22 06:09

josefdlange