views:

44

answers:

1

I'm having trouble with a simple threading example. I know my methods aren't complete, but as of now, I want to press the start button and have that fire off a thread that increments a counter every second. I know it's connected in IB correctly since the NSLog tells me it gets to my timerThread method. But then it immediately jumps back to the initial myThread, without ever reaching the updateDisplay method and releases the pool, which is why I am guessing my program doesn't actually increment a counter. I thought I would then put it in a sleep interval or something, but in the end I think I am missing the correct way to achieve this. Any thoughts would be great. Thanks!

@implementation MainController

-(id)initWithLabel:(UILabel *)label {

    if (self = [super init]) {
        countLabel = label;
        [countLabel retain];
    }
    return self;
}

-(int)count {
    return count;
}

-(void)setCount:(int) value {
    count = value;
}

-(void)updateDisplay:(NSTimer *)timer {
    NSLog(@"%s", __FUNCTION__);
    countLabel.text = [NSString stringWithFormat:@"%i", count];
    count++;
}

-(void)timerThread {
    NSLog(@"%s", __FUNCTION__);
    [NSTimer timerWithTimeInterval:1.0
                            target:self
                          selector:@selector(updateDisplay:)
                          userInfo:nil
                           repeats:YES];
    //NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];

//  threadLabel = [NSString stringWithFormat:@"%@", threadID];
}

-(void)myThread {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    //NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
    [self performSelectorOnMainThread:@selector(timerThread)
                           withObject:nil
                        waitUntilDone:NO];
//  NSLog(@"threadID in myThread: %@", threadID);
    [pool release];
}

-(void)startThread {
//  threadIndex = 0;
//  numThreads = 0;
//  NSNumber *threadID = [NSNumber numberWithInt:threadIndex++];
    [self performSelectorInBackground:@selector(myThread) withObject:nil];
//  NSLog(@"%i", threadIndex);
    numThreads++;
}

-(void)myThreadStop {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [NSThread exit];
    [self performSelectorOnMainThread:@selector(updateDisplay)
                           withObject:nil 
                        waitUntilDone:NO];

    [pool release];
}

-(void)stopThread {
    [self performSelectorInBackground:@selector(myThreadStop) withObject:nil];
}

-(void) dealloc {
    [countLabel release];
    [super dealloc];
}

@end
+2  A: 

The short answer is that you did not schedule the timer.

iOS (and others) use run loops. Each thread may have a run loop, and the main UI thread has a run loop set up for you. Simplistically, a run loop keeps a queue of things to do and either does them in an ordered fashion or blocks until there is something to do.

Anything that you do with the active UI, like setting the text of a UILabel, must be done on the main thread. In your case, you have set up (but not scheduled) a timer on the main thread to update the timer. Scheduling a timer just means adding it to the run loop.

If you had a lengthy task to perform that would update the UI at the end, you could use performSelectorInBackground to start the lengthy task and performSelectorOnMainThread when the task finishes to update UI.

If you have a short periodic task, such as updating a clock or counter UI, you can just create an NSTimer on the same thread you want the timer to fire on. When creating the timer, use a scheduledTimerWithTimeInterval variant so it will start firing automatically.

When you create a repeating timer, you must keep a reference to it so you can invalidate the timer before the target is released. At the latest, in dealloc you should invalidate the timer.

Instead of calling startThread, turn timerThread into startTimer.

-(void) startTimer {
    timerMember = [[NSTimer
      scheduledTimerWithTimeInterval:1.0
                              target:self
                            selector:@selector(updateDisplay:)
                            userInfo:nil
                             repeats:YES] retain];
}

-(void) dealloc {
     [timerMember invalidate];
     [timerMember release];
     ...
}
drawnonward