у iPhone 4 есть абсолютно определенный способ иметь долгосрочную стрельбу NSTimer - PullRequest
2 голосов
/ 09 декабря 2011

У меня постоянно возникают проблемы с моими 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 ]

4 голосов
/ 09 декабря 2011

Одной из бесчисленных проблем с NSTimers является их зависимость от цикла выполнения.Каждый поток имеет один цикл выполнения.Если вы запланируете таймер в фоновом потоке, он будет запланирован в цикле выполнения этого потока.Если этот поток является недолговечным, каковым часто являются фоновые потоки, этот таймер тихо умрет с ним.

Решение состоит в том, чтобы гарантировать, что таймер будет запущен в потоке, который будет живым, когдатаймер срабатывает.Мой лучший способ использовать эти фоновые таймеры в моем опыте - вообще не использовать NSTimer, а вместо этого использовать GCD-таймеры.Лучшие люди, чем я, закодировали таймеры на GCD.Я лично предпочитаю статью и реализацию Майка Эша , которая сопровождается объяснением.

2 голосов
/ 10 декабря 2011

Вместо этого используйте локальное уведомление .

1 голос
/ 10 декабря 2011

Пока вы зависите от использования 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

Обсуждение

[…] Вызов этой функции и ориентация на текущую очередь приводит к взаимоблокировке .

(от GCD Reference - упор мой)

...