iOS5 ARC безопасно ли планировать NSTimers из фоновых селекторов?

#objective-c #ios #ios5 #nstimer #automatic-ref-counting

#objective-c #iOS #ios5 #nstimer #автоматический подсчет ссылок

Вопрос:

Я пытаюсь отладить свое приложение.

Я использовал некоторые экземпляры NSTimer в моем коде, отличном от arc, вот так (из основного потока):

 [NSTimer scheduledTimerWithTimeInterval:5 target:musicPlayer selector:@selector(playPause:) userInfo:nil repeats:NO];
  

Это отлично работает, если я назначу этот код кнопке и нажму кнопку. Срабатывает таймер.

Я также пытался:

  if( self.deliveryTimer == nil)
  {                 
self.deliveryTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(playPause:) userInfo:nil repeats:NO];
    }

    -(void)playPause:(NSTimer*)timer
    {
           [deliveryTimer invalidate];
            deliveryTimer = nil;
//more code here

    }
  

Я бы ожидал, что таймер выполнится, нажмите метод воспроизведения / паузы ниже, затем переключитесь на nil, чтобы я мог сбросить таймер позже. Причина, по которой я проверяю значение nil, заключается в том, что у меня есть 3 разных пути кода, которые могут устанавливать таймер. У каждого есть инструкция NSLog, указывающая, что таймер был запланирован.

Мой код запускается, и я вижу, что таймеры запланированы, но, похоже, они не срабатывают в ходе обычного выполнения приложения. Я выясняю, почему. Краткосрочные таймеры, использующие ту же логику, срабатывают нормально. Проблемы возникают, когда я позволяю приложению работать некоторое время.

Могут ли NSTimers быть восстановлены ARC?

Имеет ли значение, устанавливаю ли я таймер из performSelectorInBackground? Когда я писал этот вопрос, я заметил, что некоторые из моих таймеров были созданы из пути кода, который вызывается через:

 [self performSelectorInBackground:@selector(notifyDelegateOfDataPoint:) withObject:data];
  

может ли фоновый селектор быть причиной, по которой мои таймеры не запускаются / не восстанавливаются ранее?

Приветствуется любая помощь, эта ошибка не дает мне покоя более 2 недель!

Обновление: после изменения кода для использования основного потока для NSTimers таймеры срабатывают корректно, вызывая воспроизведение музыки:

    [self performSelectorOnMainThread:@selector(deliverReminder:) withObject:nil waitUntilDone:NO];


-(void)deliverReminder:(id)sender{
     [ NSTimer scheduledTimerWithTimeInterval:10 target:reminderDeliverySystem selector:@selector(playAfterDelay:) userInfo:nil repeats:NO];
    [self postMessageWithTitle:nil message:@"Deliver Reminder Called" action:kNoContextAction];
}

-(void)playAfterDelay:(id)sender
{
    int reminderDelay = reminder.delayValue.intValue;

    [playTimers addObject:[NSTimer scheduledTimerWithTimeInterval:reminderDelay target:self selector:@selector(appMusicPlayerPlay:) userInfo:nil repeats:NO]];


}
  

Здесь у меня целая куча таймеров, это потому, что я не знаю, как передать примитив целевому объекту с помощью селектора.

Комментарии:

1. По-видимому, не срабатывают все таймеры или только некоторые? Что еще есть в их методе fire? Если таймер запланирован в фоновом потоке, он также сработает в этом потоке, что может привести к возникновению странных вещей.

2. Все таймеры, запланированные через performSelectorInBackground, действуют непоследовательно. Я слышал, как они воспроизводятся несколько раз, но в большинстве случаев они просто терпят неудачу. Я даже не получаю сообщение NSLog или обновление консоли. Вы правы насчет странных вещей, это точно описывает то, с чем я столкнулся.

Ответ №1:

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

Если вы хотите использовать свои таймеры в фоновом потоке, вы можете сделать что-то вроде следующего:

 NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
self.deliveryTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(playPause:) userInfo:nil repeats:NO];
[runLoop run];
  

Что, вероятно, происходило с запуском ваших кратковременных таймеров, а более длительных — нет, так это то, что они запускались, когда поток все еще был активен, но без цикла выполнения, чтобы поддерживать его работу, завершались сбоем после того, как поток достиг конца своего выполнения.

Я не думаю, что это связано с ARC, хотя там может быть что-то, за чем вам придется следить, потому что NSRunLoop поддерживает таймер, который к нему подключен. Выполнение стандартной процедуры с NSTimers должно избежать проблем с ARC.

Комментарии:

1. Спасибо за разъяснение, теперь я понимаю, что происходило. Что касается фонового потока, существует ли только 1 или создается новый для каждого вызова? У меня performSelectorInBackground вызывается каждые несколько секунд, и, если я правильно понимаю, я бы создавал цикл выполнения только для нескольких экземпляров, когда таймер запланирован. Эти потоки будут продолжать выполняться с циклом выполнения до срабатывания таймера. Тогда поток завершится, и его ресурсы будут восстановлены. Правильно ли это?

2. @AlexStone — Каждый из этих вызовов должен создавать новый поток. Возможно, вам потребуется вручную прервать цикл выполнения, чтобы поток завершил его. Честно говоря, если все, что вы хотите сделать, это запустить задачу в фоновом режиме после определенной задержки, я бы посмотрел на GCD dispatch_after() , используя одну из глобальных параллельных очередей. Это будет проще, и это предотвратит накладные расходы от всех этих потоков.

3. Стоит отметить две вещи о [runLoop run] : 1) управление не будет перемещаться дальше этой строки кода, пока цикл выполнения не будет завершен 2) вы не можете завершить цикл выполнения через NSRunLoop интерфейс — вам нужно получить базовый CFRunLoop и использовать CFRunLoopStop() . Просто еще больше причин использовать GCD!