Teapot
Teapot

Reputation: 59

Not working with NSThread: performSelector:withObject:afterDelay:?

is it possible that performSelector:withObject:afterDelay: doesn't work in subthreads?

I'm still new to objective c and Xcode so maybe I've missed something obvious... :/ I'd really appreciate some help.

All I want to do is to show an infolabel for 3 seconds, after that it shall be hidden. In case a new info is set the thread that hides the label after 3 seconds shall be canceled. (I don't want new information hidden through old threads.)

Sourcecode:

- (void) setInfoLabel: (NSString*) labelText
{
   // ... update label with text ...

    infoLabel.hidden = NO;

    if(appDelegate.infoThread != nil) [appDelegate.infoThread cancel]; // cancel last hide-thread, if it exists

    NSThread *newThread = [[NSThread alloc] initWithTarget: self selector:@selector(setInfoLabelTimer) object: nil];// create new thread
    appDelegate.infoThread = newThread; // save reference
    [newThread start]; // start thread


    [self performSelector:@selector(testY) withObject: nil afterDelay:1.0];

}


-(void) setInfoLabelTimer
{
    NSLog(@"setInfoLabelTimer");


    [self performSelector:@selector(testX) withObject: nil afterDelay:1.0];

    [self performSelector:@selector(hideInfoLabel) withObject: nil afterDelay:3.0];

    NSLog(@"Done?");
}

-(void) testX
{
 NSLog(@"testX testX testX testX testX");

}

-(void) testY
{
    NSLog(@"testY testY testY testY testY");

}

-(void) hideInfoLabel
{
    NSLog(@"f hideInfoLabel");
    if(!([[NSThread currentThread] isCancelled])) {
        AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
        appDelegate.infoThread = nil;
        appDelegate.infoLabel.hidden = YES;
        [NSThread exit];
    }
}

Console-Output:

As you can see performSelector:withObject:afterDelay: DOES work (--->"testY testY testY testY testY"), but not in the subthread (which runs (--->"setInfoLabelTimer" and"Done?"))

Does anyone know why performSelector:withObject:afterDelay: doesn't work in subthreads? (Or what's my fault? :()

Best regards, Teapot

Upvotes: 0

Views: 1942

Answers (4)

bbum
bbum

Reputation: 162712

There is no need for threads or GCD at all to do what you want to do.

Simply use performSelector:withObject:afterDelay: directly on the main thread, us an animation as @Rob indicated, use dispatch_after on the main queue, or an NSTimer.

Upvotes: 0

hrchen
hrchen

Reputation: 1223

If you want to call performSelector:withObject:afterDelay on thread, this thread must has a running RunLoop. Check out the Thread Programming Guide from Apple. Here is also an example for RunLoop and NSThread.

You can add the following code in the setInfoLabelTimer:

while (!self.isCancelled)
{
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                            beforeDate:[NSDate distantFuture]];
}

Upvotes: 1

Rob
Rob

Reputation: 437632

As an aside, you might want to consider working with Grand Central Dispatch, GCD, instead. If you want to do something in three seconds, you can:

double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    // do stuff here, and because it's in the main queue, you can do UI stuff, too
});

I'd also refer you to Migrating Away From Threads in the Concurrency Programming Guide.


Alternatively, rather than using a GCD, you can use an animation block, in which you can designate what you want to happen in 3.0 seconds. You can also animate that transition (in my example, 0.25 seconds), so that the removal of the control is a little more graceful:

[UIView animateWithDuration:0.25
                      delay:3.0
                    options:0
                 animations:^{
                     // you can, for example, visually hide in gracefully over a 0.25 second span of time

                     infoLabel.alpha = 0.0;
                 }
                 completion:^(BOOL finished) {
                     // if you wanted to actually remove the view when the animation was done, you could do that here

                     [infoLabel removeFromSuperview];
                 }];

Upvotes: 1

Wain
Wain

Reputation: 119031

If you're running a 'sub' thread (a thread which isn't the main thread) it can run in one of 2 ways:

  1. It runs a single method and then terminates
  2. It runs a run loop and handles items from a queue

If the thread runs in form 1, your use of performSelector puts an item onto a queue (or tries to at least) but it will never get handled, the thread will just terminate.

If you wanted to use performSelector on the thread you'd need to do additional work. Or, you could push the item onto the main thread where a run loop is running.

Upvotes: 1

Related Questions