iPhone 4有一个绝对肯定的方式来长期使用NSTimer

时间:2011-12-09 17:26:55

标签: iphone objective-c ios multithreading nstimer

我的NSTimers和背景选择器一直存在问题。它让我疯狂,需要很长时间来尝试每个调整。为了保持我的理智和未来几代可可程序员的理智,我提出了这个问题:

是否有一种绝对100%可靠的方法可以在以后的某个时间点启动计划的长期计时器,无论是从后台线程,主线程等调用它?

似乎我一直在为使用NSTimers的大多数类一遍又一遍地解决同样的问题。它们在短期测试期间工作,让我们说我将计时器设置为通过后台线程在10秒内触发。它有效,因为还有一个运行循环运行。但是,一旦我把火的时间改为我真正想要的,比如15-30分钟,就会有沉默。运行循环消失了,我不知道如何处理这种情况。没有任何事情发生,几天后我发现了这样的错误,一旦我已经忘记了哪个计时器将对此负责。

目前我正在与选择者一起做一些非常非常丑陋的舞蹈,例如这里的测试方法(它似乎适用于10分钟的计时器):

//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]];

    }

我喜欢不必担心使用预定的计时器创建一个火灾日期对象的便利,但是我应该忘记它们并使用具有特定火灾日期的计时器吗?似乎scheduleTimer适用于短期任务,当运行循环已经存在时,但我在应用程序执行期间根本看不到这种错误。有一次,似乎定时器正常射击,但是稍后它们会完全停止射击。

感谢您的帮助或澄清。 我正在寻找一种方法来安排计时器,而不必担心每次我需要安排计时器时是否存在运行循环。我想确保只要应用程序正在运行,我通过此方法安排的计时器将在未来的可预测点触发。

3 个答案:

答案 0 :(得分:4)

NSTimers的一个无数问题是它们的运行循环依赖性。每个线程都有一个运行循环。如果您在后台线程上安排计时器,它将在该线程的运行循环上进行调度。如果该线程是短暂的,通常是哪个后台线程,那么该计时器将悄然死亡。

解决方案是保证计时器在计时器触发时处于活动状态的线程上运行。根据我的经验,这些专用背景计时器的最佳方法是根本不使用NSTimer,而是使用GCD计时器。比我编写GCD动力定时器的人更好。我个人更喜欢Mike Ash's文章和实现,附带解释。

答案 1 :(得分:2)

改为使用local notification

答案 2 :(得分:1)

只要你依赖scheduledTimerWithTimeInterval:...,你就无法达到你想要的效果:
计时器将始终与调用线程的运行循环绑定。

如果在该消息调用之前没有与该线程关联的运行循环,那么当方法返回时肯定会有一个运行循环,因为-[NSRunLoop currentRunLoop]会在必要时创建一个运行循环。

可以做什么,如果你不喜欢用于创建计时器的其他API,则在NSTimer上提供一个类别,它负责所有的日程安排,所以并且您可以在其他项目中重复使用。

以下是可能类别的示例:

#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();
}

在使用dispatch_sync之前请注意线程检查,因为......

  

dispatch_sync

     

讨论

     

[...]调用此函数并定位当前队列会导致死锁

(来自The GCD Reference - 强调我的)