Dispatch Queues and Run Loop Modes

April 7, 2014

Have you ever wondered which run loop modes a block will execute in if you submit the block for asynchronous execution on the main queue? Apparently, not many people have wondered this. (That's why you have me.) And of course, it's not documented by Apple. (That's why you have … me.)

Most people would answer this question by guessing. And they may even guess right. But I only bet on sure things. Like the Patriots in Super Bowl XLII.

To get a definitive answer, you must follow the scientific method. Ideally, this means asking Neil deGrasse Tyson. He's not answering my phone calls anymore, however, so I had to resort to some testing:

-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
	dispatch_async( dispatch_get_main_queue(), ^{
		NSLog( @"%@", [[NSRunLoop currentRunLoop] currentMode] );
	} );
	
	switch ( 1 )
	{
		case 1: // No output
		{
			[[NSMachPort port] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:@"My Custom Mode"];
			[[NSRunLoop currentRunLoop] runMode:@"My Custom Mode" beforeDate:[NSDate distantFuture]];
		}
			break;
			
		case 2: // "My Custom Mode"
		{
			CFRunLoopAddCommonMode( [[NSRunLoop currentRunLoop] getCFRunLoop], CFSTR( "My Custom Mode" ) );
			[[NSMachPort port] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:@"My Custom Mode"];
			[[NSRunLoop currentRunLoop] runMode:@"My Custom Mode" beforeDate:[NSDate distantFuture]];
		}
			break;
			
		case 3: // "NSModalPanelRunLoopMode"
		{
			[[NSSavePanel savePanel] runModal];
		}
			break;
			
		default: // "kCFRunLoopDefaultMode"
			break;
	}
}

It turns out that the block will only execute in the "common" run loops modes, e.g., NSDefaultRunLoopMode, NSModalPanelRunLoopMode, and NSEventTrackingRunLoopMode. It won't execute in any custom mode that you've defined, unless you add your custom mode to the common modes.

By the way, you might be wondering about why I add an NSMachPort to the run loop above. This is a trick to keep the loop running in my custom mode until the "distant future". If there are no input sources associated with a run loop mode, then the loop will exit immediately when you try to run it. So the NSMachPort is just a dummy input source. It never actually provides input, it exists only so that the run loop waits for input.

I think there's a tendency among developers to think that libdispatch is newer and thus somehow independent of all the old AppKit garbage such as run loops, but I've proved that this is not the case. We're still knee-deep in garbage. What an incredible smell I've discovered!