блок dispatch_after не запущен - PullRequest
1 голос
/ 05 ноября 2019

Пожалуйста, рассмотрите этот простой пример:

- (void)viewDidLoad
{
    [super viewDidLoad];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"BLOCK!!!");

    });

    while (YES)
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        NSLog(@"RUN LOOP");
    }
});
}

Блок, переданный во второй вызов (3 секунды) на dispatch_after, не запускается. Однако, если я не использую первый dispatch_after (2 секунды), он работает как положено. Почему?

Я знаю, что если я удаляю цикл while с NSRunLoop, работающим внутри, то он работает, но мне нужен цикл там

Ответы [ 2 ]

1 голос
/ 05 ноября 2019

У вас есть код, который

  • планирует dispatch_after для запуска в главной очереди;но затем
  • блокирует основную очередь с помощью цикла while, который неоднократно вызывает NSRunLoop.

Это просто блокирует основной поток от выполнения чего-либо, что невызывается непосредственно из основного NSRunLoop.

. Есть три решения этой проблемы:

  1. Это можно исправить, отправив код с циклом while вглобальная (т.е. фоновая) очередь:

    - (void)viewDidLoad{
        [super viewDidLoad];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
            NSLog(@"OUTER");
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"INNER!!!");
            });
    
            while (true) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
            }
        });
    }
    

    Этот метод является перестановкой того, что мы привыкли делать в дни до GCD. В настоящее время это в значительной степени бесполезно. Это слишком неэффективно.

  2. Вы можете использовать NSTimer, который запланирован и выполняется из NSRunLoop, поэтому, пока вы все еще блокируете основную очередь, по крайней мере,Таймер сработает.

    - (void)viewDidLoad{
        [super viewDidLoad];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"OUTER");
    
            [NSTimer scheduledTimerWithTimeInterval:3 repeats:false block:^(NSTimer * _Nonnull timer) {
                NSLog(@"INNER!!!");
            }];
    
            while (true) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
            }
        });
    }
    

    Это не совсем решение проблемы (вы по-прежнему блокируете основной поток от всего, что не запускается из самого NSRunLoop), но освещает природуrunloop.

  3. Или, очевидно, лучше просто удалить цикл while:

    - (void)viewDidLoad{
        [super viewDidLoad];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"OUTER");
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"INNER!!!");
            });
        });
    }
    

Итог, в настоящее время выпрактически никогда не вращается на нити (или ее runloop). Это ужасно неэффективно, и GCD предлагает гораздо более элегантные способы достижения желаемого эффекта.

1 голос
/ 05 ноября 2019

Я не вижу ни одного зарегистрированного источника входного сигнала в образце. Я не думаю, что отправка считается источником ввода.

В этом случае -runMode:beforeDate: немедленно вернет NO. Настроенный бесконечный цикл будет просто вращаться, даже не выходя из текущего цикла. Он никогда не возвращается на вершину цикла выполнения и никогда не обрабатывает ни одну из очередей отправки.

В runMode: beforeDate: , определен behvior.

Обсуждение

Если к циклу выполнения не подключены входные источники или таймеры, этот метод немедленно завершается и возвращает NO;в противном случае он возвращается после обработки первого входного источника или достижения limitDate. Удаление всех известных входных источников и таймеров из цикла выполнения вручную не гарантирует немедленного завершения цикла выполнения. macOS может устанавливать и удалять дополнительные входные источники по мере необходимости для обработки запросов, направленных на поток получателя. Поэтому эти источники могут препятствовать выходу из цикла выполнения.

...