简体   繁体   中英

iPhone 4 is there an absolutely certain way to have a long term NSTimer fire

I keep having troubles with my NSTimers and background selectors. It is driving me nuts and takes a very long time to try out each tweak. To preserve my sanity, and the sanity of future generations of cocoa programmers, I'm asking this question:

Is there an absolutely 100% sure way to have a scheduled, long-term timer fire at a later point in time, regardless of whether it was called from a background thread, main thread, etc?

It seems that I keep having to solve the same problem over and over again for the majority of my classes that use NSTimers. they work during short-term testing, let's say I set the timer to fire through a background thread to fire in 10 seconds. It works, because there's still a run loop running. But once I change the fire time to what I really want, like 15-30 minutes, there's dead silence. The run loop is gone and I don't know how to handle such a case. Nothing happens, and I discover such bugs a few days later, once I've already forgotten which timer would be responsible for that.

Currently I'm doing some really, really ugly dance with selectors, for example here's a test method(It seems to work for 10 minute timers):

//this is a test method to simulate a background task requesting a timer
  [self performSelectorInBackground:@selector(backgroundReminderLongTermTest:) withObject:nil];

//this is a method similar to the one that the background thread would be trying to invoke
    -(void)backgroundReminderLongTermTest:(id)sender
    {
        [self performSelectorOnMainThread:@selector(backgroundReminderFromMainThread:) withObject:nil waitUntilDone:NO];
    }

//this is a wrapper for the background method, I want the timer to be added to a thread with a run loop already established and running
    -(void)backgroundReminderFromMainThread:(id)sender
    {
            [playTimers addObject:[NSTimer scheduledTimerWithTimeInterval:1800 target:self selector:@selector(start:) userInfo:nil repeats:NO]];

    }

I like the convenience of not having to worry about creating a fire date object with the scheduled timers, but should I just forget about them and use timers with specific fire dates? It seems that the scheduledTimer works well for short term tasks, when the run loop is already present, but I simply cannot see this kind of bugs during the app's execution. At one point, it seems that the timers are firing normally, but at a later point they stop firing completely.

Thank you for any help or clarification. I'm looking for a method that schedules timers without having to worry about whether or not a run loop is present every time I need to schedule a timer. I want to be sure that as long as the app is running, my timers, scheduled through this method would fire at predictable points in the future .

One of the myriad issues with NSTimers is their run-loop dependency. Every thread has a single run loop. If you schedule a timer on a background thread, it will be scheduled on that thread's run loop. If that thread is short lived, which background threads often are, that timer will quietly die with it.

The solution is to guarantee the timer is run on a thread that will be alive when the timer fires. The best way to do these dedicated background timers in my experience is to not use NSTimer at all, and go for GCD timers instead. Better men than I have coded up GCD powered timers. I personally prefer Mike Ash's article and implementation, which comes with an explanation.

请改用本地通知

For as long as you depend on using scheduledTimerWithTimeInterval:... you cannot achieve what you want:
The timer will always be tied to the run-loop of the calling thread.

If there is no run-loop associated with that thread by the time of that message's invocation, there surely is one when the method returns as -[NSRunLoop currentRunLoop] creates a run-loop if necessary.

What you can do, if you don't like the other APIs for creation of a timer, is providing a category on NSTimer , which takes care of all the scheduling and so forth and that you can reuse in other projects.

Here is an example of what such a category might look like:

#pragma mark - setting up a timer:
+ (NSTimer *)yourPrefix_mainLoopScheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
    NSTimer *timer = [self yourPrefix_timerWithTimeInterval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];

    void (^scheduler)() = ^{
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    };

    if ([NSThread isMainThread]) {
        scheduler();
    } else {
        // you should really be able to rely on the fact, that the timer is ready to roll, when this method returns
        dispatch_sync(dispatch_get_main_queue(), scheduler);
    }

    return timer;
}

// this is just a convenience for the times where you actually want an _unscheduled_ timer
+ (NSTimer *)yourPrefix_timerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
    NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:interval];

    NSTimer *timer = [[self alloc] initWithFireDate:fireDate interval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];

    return [timer autorelease];
}

#pragma mark - tearing it down:
- (void)yourPrefix_invalidateMainLoopTimer
{
    [self yourPrefix_invalidateMainLoopTimerAsynchronous:NO];
}

- (void)yourPrefix_invalidateMainLoopTimerAsynchronous:(BOOL)returnsImmediately
{
    void (^invalidator)() = ^{
        [self invalidate];
    };

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    if (returnsImmediately) {
        dispatch_async(mainQueue, invalidator);
        return;
    }

    if (![NSThread isMainThread]) {
        dispatch_sync(mainQueue, invalidator);
        return;
    }

    invalidator();
}

Note the thread checks before using dispatch_sync because...

dispatch_sync

Discussion

[…] Calling this function and targeting the current queue results in deadlock .

(from The GCD Reference — emphasis mine)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM