What I want to do is create an indirect queue targeting the main queue.
dispatch_queue_t myQueue = dispatch_queue_create("com.mydomain.my-main-queue", NULL);
dispatch_set_target_queue(myQueue, dispatch_get_main_queue());
My ultimate goal is to use the queue as the underlyingQueue property of an NSOperationQueue, because Apple's documentation clearly states not to use dispatch_get_main_queue(). Though using an indirect queue it technically is following the documentation.
The reason for all this is because NSOperationQueue.mainQueue is not a safe for asynchronous operations, because it is globally accessible and it's maxConcurrentOperationCount is set to 1. So can easily shoot yourself in the foot with this operation queue.
Update 1
It seems there is a lot of confusion about the basis of what this question assumes an "asynchronous NSOperation" is. To be clear this is based on the concepts in this WWDC session The particular concept is using "operation readiness" and dependency management to manage the tasks in your app, which means asynchronous NSOperations are added to NSOperationQueues to take advantage of this. If you take these concepts to the spirit of this question hopefully the reasoning will make more sense, and you can focus on comparing and contrasting the solution with other ones.
Update 2 - Example of issue:
// VendorManager represents any class that you are not in direct control over.
@interface VendorManager : NSObject
@end
@implementation VendorManager
+ (void)doAnsyncVendorRoutine:(void (^)(void))completion {
// Need to do some expensive work, make sure we are off the main thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND 0), ^(void) {
// Some off main thread background work
sleep(10);
// We are done, go back to main thread
[NSOperationQueue.mainQueue addOperationWithBlock:completion];
});
}
@end
// MYAsyncBoilerPlateOperation represents all the boilerplate needed
// to implement a useful asnychronous NSOperation implementation.
@interface MYAlertOperation : MYAsyncBoilerPlateOperation
@end
@implementation MYAlertOperation
- (void)main {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:"Vendor"
message:"Should vendor do work?"
preferredStyle:UIAlertControllerStyleAlert];
__weak __typeof(self) weakSelf = self;
[alertController addAction:[UIAlertAction actionWithTitle:@"Yes"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[VendorManager doAnsyncVendorRoutine:^{
// implemented in MYAsyncBoilerPlateOperation
[weakSelf completeThisOperation];
}];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"No"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[weakSelf cancel];
}]];
[MYAlertManager sharedInstance] presentAlert:alertController animated:YES];
}
@end
// MYAlertOperation will never complete.
// Because of an indirect dependency on operations being run on mainQueue.
// This example is an issue because mainQueue maxConcurrentOperationCount is 1.
// This example would not be an issue if maxConcurrentOperationCount was > 1.
[NSOperationQueue.mainQueue addOperation:[[MYAlertOperation alloc] init]];
Update 3 - Example 2:
I am not showing the implementation of MyAsyncBlockOperation but you can use this as what it's based on in Swift.
// operation.asynchronous is YES, and things are implemented correctly for state changes.
MyAsyncBlockOperation *operation = [MyAsyncBlockOperation new];
__weak MyAsyncBlockOperation *weakOperation = operation;
// executionBlock is simply invoked in main
// - (void)main { self.executionBlock() };
operation.executionBlock = ^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Vendor"
message:@"Should vendor do work?"
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"Yes"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"Never called");
[weakOperation completeWithSuccess];
}];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"No"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[weakOperation cancel];
}]];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}];
operation.completionBlock = ^{
NSLog(@"If YES, Never called. If NO, called.");
};
[[NSOperationQueue mainQueue] addOperation:operation];
So I thought, why not have another NSOperationQueue? One with it's underlyingQueue set to the previously mentioned indirect GCD queue (still following the documentation). So we can have a concurrent NSOperationQueue, legally targeting the serial main GCD queue, and ultimately ensuring the operations run on the main thread.
Let me know if you want clarification, here is an example of the full code:
NSOperationQueue *asyncSafeMainQueue = [[NSOperationQueue alloc] init];
asyncSafeMainQueue.qualityOfService = NSQualityOfServiceDefault; // not needed, just for clarity
dispatch_queue_t underlyingQueue = dispatch_queue_create("com.mydomain.main-thread", NULL);
dispatch_set_target_queue(underlyingQueue, dispatch_get_main_queue());
asyncSafeMainQueue.underlyingQueue = underlyingQueue;
Now... there is a safe operation queue for asynchronous operations that need to run on the main thread, and without any unnecessary context switching.
Is it safe?
See Question&Answers more detail:
os