请考虑这个简单的例子:
- (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循环,那么它就可以工作了,但我需要那里的循环
您有
- 调度
dispatch_after
在主队列上运行;但是后来 - 用重复调用CCD_ 6的CCD_
这只是阻止主线程执行任何不是直接从主NSRunLoop
调用的操作。
这个问题有三种解决方案:
-
您可以通过将带有
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之前所做的事情的一种排列。如今,这在很大程度上变得毫无用处。只是效率太低了。
-
您可以使用从
NSRunLoop
调度和运行的NSTimer
,因此,当您仍在阻塞主队列时,至少计时器会启动。- (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
本身运行的任何东西(,但它说明了运行循环的性质。 -
或者,很明显,最好只删除
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提供了更优雅的方式来达到预期的效果。
我在示例中没有看到任何注册的输入源。我不认为调度是一个输入源。
在这种情况下,-runMode:beforeDate:
将立即返回NO
。您设置的无限循环将在不退出当前运行的情况下旋转。它永远不会回到运行循环的顶部,也永远不会处理任何调度队列。
在runMode:beforeDate:中,行为被定义。
讨论
如果没有输入源或计时器连接到运行循环,则此方法立即退出并返回no;否则,它将在处理完第一个输入源或达到limitDate之后返回。手动从运行循环中删除所有已知的输入源和计时器并不能保证运行循环会立即退出。macOS可以根据需要安装和移除额外的输入源,以处理针对接收器线程的请求。因此,这些源可以阻止运行循环退出。