Tim Windsor Brown
Tim Windsor Brown

Reputation: 4089

Accessing UIcomponents with multiple threads

I'm really struggling with a multi-thread program that changes an IBOutlet when external variables change.

With class header:

@interface QuestionViewController : UIViewController{

// IBOutlet
IBOutlet UIBarButtonItem *changeButton;

...

}

Starting the thread:

    // Starts new thread with method 'statusPollMethod' to determine when status 
    [NSThread detachNewThreadSelector:@selector(statusPollMethod:) toTarget:self withObject: @"Voting"];

Begins:

-(void)statusPollMethod:requiredStatus{

GetSessionDataApi *statusPoll = [GetSessionDataApi new];
SessionCache *tempSessionCache = [SessionCache new];

...

@synchronized(self){
// Infinite Loop
while(1) {

    // If sessionStatus is 'Voting' set button as displayed
    if ([[tempSessionCache sessionStatus] isEqualToString: (@"Voting")]){
        NSLog(@"Status is Voting!");
        // Ungreys and sets the button
        [changeButton setTitle:@"Submit"];
            changeButton.enabled = YES; 
    }

    // Wait for around 3 seconds
    [NSThread sleepForTimeInterval:3]; 

    }
   }
   return;
}

The problem is that the 2nd thread only occasionally completes any action on the IBOutlet. The IBOutlet is also accessed by the 1st thread, so I'm aware I probably have some multi-threading issues and I'm unsure how to protect the IBOutlet from this.

As advised, I've looked through Apple Docs/Overflow posts, but I'm still confused.

I really appreciate your help!

Tim

Upvotes: 0

Views: 87

Answers (2)

benzado
benzado

Reputation: 84398

UI components are not thread safe. The main thread uses a run loop (see NSRunLoop) to call out to your code (where you change UI state), then it updates the screen on each pass through the loop. Messing around with UI components from any other thread will screw things up.

You need to put the code that changes the button title/enabled in a separate method, and signal the main thread to call it. You can do that with performSelectorOnMainThread:withObject:waitUntilDone:. This essentially creates a single-use NSTimer ands schedules it to fire immediately on the main thread's run loop, so the main thread will call the method as soon as it can.

You have the option of suspending your second thread until the main thread is finished with the call (waitUntilDone:). I'd recommend NOT waiting if you're just updating UI state, since you don't need any information to be passed back from the UI to your worker thread.

Upvotes: 1

Paul Cezanne
Paul Cezanne

Reputation: 8741

Have you tried performSelectorOnMainThread?

    [self performSelectorOnMainThread:@selector(doUIOnMainThread) withObject:nil waitUntilDone:NO];

and then my method doUIOnMainThread does the UI things.

Upvotes: 1

Related Questions