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

╄→гoц情女王★ 提交于 2019-12-01 11:35:54

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)

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.

Use local notification instead.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!