2

请考虑这个简单的例子:

- (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在里面运行,那么它可以工作,但我需要那里的循环

4

2 回答 2

2

你有代码

  • 调度 adispatch_after在主队列上运行;但是之后
  • 用一个while重复调用NSRunLoop.

这只是阻止主线程做任何不是直接从 main 调用的事情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自身运行的东西),但它说明了运行循环的性质。

  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 提供了更优雅的方式来实现所需的效果。

于 2019-11-05T15:59:23.060 回答
1

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

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

runMode:beforeDate:中,定义了行为。

讨论

如果没有输入源或计时器附加到运行循环,则此方法立即退出并返回 NO;否则,它在处理第一个输入源或达到 limitDate 后返回。从运行循环中手动删除所有已知的输入源和计时器并不能保证运行循环将立即退出。macOS 可能会根据需要安装和删除其他输入源,以处理针对接收者线程的请求。因此,这些来源可能会阻止运行循环退出。

于 2019-11-05T15:17:39.510 回答