objective-cmultithreadingxcodeiosnsthread

Objective-C: Blocking a Thread until an NSTimer has completed (iOS)


I've been searching for and attempting to program for myself, an answer to this question.

I've got a secondary thread running inside my mainView controller which is then running a timer which counts down to 0.

Whilst this timer is running the secondary thread which initiated the timer should be paused/blocked whatever.

When the timer reaches 0 the secondary thread should continue.

I've Experimented with both NSCondition and NSConditionLock with no avail, so id ideally like solutions that solve my problem with code, or point me to a guide on how to solve this. Not ones that simply state "Use X".

- (void)bettingInit {    
    bettingThread = [[NSThread alloc] initWithTarget:self selector:@selector(betting) object:nil];
    [bettingThread start];
}

- (void)betting {

    NSLog(@"betting Started");
    for (int x = 0; x < [dealerNormalise count]; x++){
        NSNumber *currSeat = [dealerNormalise objectAtIndex:x];
        int currSeatint = [currSeat intValue];
        NSString *currPlayerAction = [self getSeatInfo:currSeatint objectName:@"PlayerAction"];
        if (currPlayerAction != @"FOLD"){
            if (currPlayerAction == @"NULL"){                
                [inactivitySeconds removeAllObjects];
                NSNumber *inactivitySecondsNumber = [NSNumber numberWithInt:10];
                runLoop = [NSRunLoop currentRunLoop];
                betLooper = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(betLoop) userInfo:nil repeats:YES];
                [runLoop addTimer:[betLooper retain] forMode:NSDefaultRunLoopMode];
                [runLoop run];

                // This Thread needs to pause here, and wait for some input from the other thread, then continue on through the for loop

                NSLog(@"Test");
            }
        }
    }
}

- (void)threadKiller {
    [betLooper invalidate];

    //The input telling the thread to continue can alternatively come from here
    return;
}

- (void)betLoop {
    NSLog(@"BetLoop Started");
    NSNumber *currentSeconds = [inactivitySeconds objectAtIndex:0];
    int currentSecondsint = [currentSeconds intValue];
    int newSecondsint = currentSecondsint - 1;
    NSNumber *newSeconds = [NSNumber numberWithInt:newSecondsint];
    [inactivitySeconds replaceObjectAtIndex:0 withObject:newSeconds];
    inacTimer.text = [NSString stringWithFormat:@"Time: %d",newSecondsint];                      

    if (newSecondsint == 0){

        [self performSelector:@selector(threadKiller) onThread:bettingThread  withObject:nil waitUntilDone:NO];

        // The input going to the thread to continue should ideally come from here, or within the threadKiller void above
    }
}

Solution

  • You can't run a timer on a thread and sleep the thread at the same time. You may want to reconsider whether you need a thread at all.

    There's a few things that need to be pointed out here. First, when you schedule your timer:

    betLooper = [NSTimer scheduledTimerWithTimeInterval:1 
                                                 target:self 
                                               selector:@selector(betLoop:) 
                                               userInfo:nil 
                                                repeats:YES];
    

    it's added to and retained by the current run loop by that method, so you don't need to do that manually. Just [myRunLoop run]. Your timer's selector argument is also invalid -- a timer's "target method" needs to look like this:

    - (void)timerFireMethod:(NSTimer *)tim;
    

    This also means that you don't need to retain the timer if all you want to do is invalidate it, since you will have a reference to it from inside that method.

    Second, it's not clear what you mean by "this thread needs to sleep to wait for input". When you schedule that timer, the method (betLoop) is called on the same thread. If you were to sleep the thread, the timer would stop too.

    You seem to be a little mixed up regarding methods/threads. The method betting is running on your thread. It is not itself a thread, and it's possible to call other methods from betting that will also be on that thread. If you want a method to wait until another method has completed, you simply call the second method inside the first:

    - (void)doSomethingThenWaitForAnotherMethodBeforeDoingOtherStuff {
        // Do stuff...
        [self methodWhichINeedToWaitFor];
        // Continue...
    }
    

    I think you just want to let betting return; the run loop will keep the thread running, and as I said, the other methods you call from methods on the thread are also on the thread. Then, when you've done the countdown, call another method to do whatever work needs to be done (you can also invalidate the timer inside betLoop:), and finalize the thread:

    - (void)takeCareOfBusiness {
      // Do the things you were going to do in `betting`
      // Make sure the run loop stops; invalidating the timer doesn't guarantee this
      CFRunLoopStop(CFRunLoopGetCurrent());    
      return;    // Thread ends now because it's not doing anything.
    }
    

    Finally, since the timer's method is on the same thread, you don't need to use performSelector:onThread:...; just call the method normally.

    You should take a look at the Threading Programming Guide.

    Also, don't forget to release the bettingThread object that you created.