ios repeats NSTimer требует от меня добавить его в runloop




obj c nstimer (5)

Метод таймера вызываться не будет, поскольку очереди GCD создают потоки без циклов выполнения

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
   [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
       NSLog(@"Timer method from GCD main queue");
   }];

});

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

dispatch_async(dispatch_get_main_queue(), ^{
   [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
       NSLog(@"Timer method from GCD main queue");
   }];

});

Мне интересно, может ли кто-нибудь объяснить, почему для отправки обратно в основную очередь и создания повторяющегося NSTimer мне нужно добавить его в RUN LOOP, чтобы он тоже срабатывал? Даже при использовании performselectorOnMainThread мне все равно нужно добавить его в RUN LOOP, чтобы запустить его.

Ниже приведен пример моего вопроса:

#define queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define mainqueue dispatch_get_main_queue()

- (void)someMethodBeginCalled
{
    dispatch_async(queue, ^{
        int x = 0;
        dispatch_async(mainqueue, ^(void){
            if([_delegate respondsToSelector:@selector(complete:)])
                [_delegate complete:nil];
        });
    });
}

- (void)compelete:(id)object
{
    [self startTimer];

    //[self performSelectorOnMainThread:@selector(startTimer) withObject:nil waitUntilDone:NO];
}

- (void)startTimer
{
    NSTimer timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(callsomethingelse) userInfo:nil repeats:YES];

    //NSDefaultRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes];
}

РЕДАКТИРОВАТЬ: Я думаю, что я сформулировал этот вопрос очень плохо. Я хотел бы знать, почему [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes]; необходимо в startTimer если я вызываю someMethodBeginCalled . Если я не включу эту строку, таймер не сработает.

Например, если я вызываю startTimer из viewDidLoad , я могу удалить строку NSRunLoop и таймер будет срабатывать каждые 60 секунд.


Answer #1

Как сказал @sosborn, NSTimer зависят от NSRunLoop , и, поскольку очереди GCD создают потоки, которые не имеют циклов выполнения, NSTimer плохо работает с GCD .

Ознакомьтесь с другим вопросом по этому вопросу: безопасно ли планировать и аннулировать NSTimers в очереди GCD?

Чтобы решить эту проблему, я реализовал MSWeakTimer : https://github.com/mindsnacks/MSWeakTimer (и проверка была проверена инженером libdispatch на последнем WWDC!)


Answer #2

А вот как добавить NSTimer в runloop:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];

Answer #3

Добавление таймера в runloop не сработало в моем случае. Я должен был создать таймер в главном потоке. Я делал это создание потока в делегате MultipeerConnectivity.

    dispatch_async(dispatch_get_main_queue(), ^{
        self.timer = [NSTimer scheduledTimerWithTimeInterval:self.interval  invocation: self.invocation repeats:YES];
    });

Answer #4

Потому что, как говорят docs :

Таймеры работают в сочетании с циклами выполнения. Чтобы эффективно использовать таймер, вы должны знать о том, как работают циклы выполнения - см. NSRunLoop и Руководство по программированию Threading. В частности, обратите внимание, что циклы выполнения сохраняют свои таймеры, поэтому вы можете освободить таймер после добавления его в цикл выполнения.

Это дизайнерское решение, которое Apple приняла, когда они написали код для NSTimer (и я уверен, что у них были веские причины для этого), и мы ничего не можем сделать, чтобы обойти это. Это действительно так обременительно?







nsrunloop