Please consider this simple example:
- (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");
}
});
}
The block passed into the second call (3 seconds) to dispatch_after
is not fired. However if I don't use the first dispatch_after
(2 seconds) then it works as expected. Why?
I know that if I remove the while loop with NSRunLoop
running inside then it is working but I need the loop there
You have code which
dispatch_after
to run on the main queue; but thenwhile
loop that is repeatedly calling the NSRunLoop
.This is just blocking the main thread from doing anything that isn’t invoked directly from the main NSRunLoop
.
There are three solutions to this problem:
You can fix this by dispatching the code with the while
loop to a global (i.e. background) queue:
- (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]];
}
});
}
This technique is is a permutation of something we used to do in the pre-GCD days. This is rendered largely useless nowadays. It’s just too inefficient.
You can use a NSTimer
which is scheduled and run from the NSRunLoop
, so, while you’re still blocking the main queue, at least the timer will fire.
- (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]];
}
});
}
This not really a solution to the problem (you’re still blocking the main thread from anything not running from the NSRunLoop
itself), but is illuminating about the nature of the runloop.
Or, obviously, it’s best to just remove the while
loop:
- (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!!!");
});
});
}
Bottom line, nowadays, you practically never spin on a thread (or its runloop). It’s terribly inefficient and GCD offers far more elegant ways to achieve the desired effect.