dispatch_after 块不是 运行

dispatch_after block is not running

请考虑这个简单的例子:

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

传递给对 dispatch_after 的第二次调用(3 秒)的块未被触发。但是,如果我不使用第一个 dispatch_after (2 秒),那么它会按预期工作。为什么?

我知道,如果我删除带有 NSRunLoop 运行 的 while 循环,那么它可以正常工作,但我需要那里的循环

我在示例中没有看到任何已注册的输入源。我不认为调度算作输入源。

在这种情况下,-runMode:beforeDate: 会立即 return NO。您设置的无限循环只会旋转而不会退出当前 运行。它永远不会回到 运行 循环的顶部,也永远不会处理任何调度队列。

runMode:beforeDate:中定义了行为。

Discussion

If no input sources or timers are attached to the run loop, this method exits immediately and returns NO; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop does not guarantee that the run loop will exit immediately. macOS may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

您有代码

  • 在主队列上安排 dispatch_after 到 运行;但后来
  • 用重复调用 NSRunLoop.
  • while 循环阻塞主队列

这只是阻止主线程执行任何未直接从主线程调用的操作 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. 您可以使用已调度的 NSTimerNSRunLoop 中的 运行,因此,当您仍在阻塞主队列时,至少计时器将启动。

    - (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 本身的 运行ning),但它阐明了 运行 本身的性质=54=]循环.

  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!!!");
            });
        });
    }
    

归根结底,如今,您几乎从不在线程(或其 运行 循环)上旋转。这是非常低效的,GCD 提供了更优雅的方法来达到预期的效果。