It's set to 1 because there's no reason to set it to anything else, and it's probably slightly better to keep it set to 1 for at least three reasons I can think of.
Reason 1
Because NSOperationQueue.mainQueue
's underlyingQueue
is dispatch_get_main_queue()
, which is serial, NSOperationQueue.mainQueue
is effectively serial (it could never run more than a single block at a time, even if its maxConcurrentOperationCount
were greater than 1).
We can check this by creating our own NSOperationQueue
, putting a serial queue in its underlyingQueue
target chain, and setting its maxConcurrentOperationCount
to a large number.
Create a new project in Xcode using the macOS > Cocoa App template with language Objective-C. Replace the AppDelegate
implementation with this:
@implementation AppDelegate {
dispatch_queue_t concurrentQueue;
dispatch_queue_t serialQueue;
NSOperationQueue *operationQueue;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
concurrentQueue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
serialQueue = dispatch_queue_create("q2", nil);
operationQueue = [[NSOperationQueue alloc] init];
// concurrent queue targeting serial queue
//dispatch_set_target_queue(concurrentQueue, serialQueue);
//operationQueue.underlyingQueue = concurrentQueue;
// serial queue targeting concurrent queue
dispatch_set_target_queue(serialQueue, concurrentQueue);
operationQueue.underlyingQueue = serialQueue;
operationQueue.maxConcurrentOperationCount = 100;
for (int i = 0; i < 100; ++i) {
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation %d starting", i);
sleep(3);
NSLog(@"operation %d ending", i);
}];
[operationQueue addOperation:operation];
}
}
@end
If you run this, you'll see that operation 1 doesn't start until operation 0 has ended, even though I set operationQueue.maxConcurrentOperationCount
to 100. This happens because there is a serial queue in the target chain of operationQueue.underlyingQueue
. Thus operationQueue
is effectively serial, even though its maxConcurrentOperationCount
is not 1.
You can play with the code to try changing the structure of the target chain. You'll find that if there is a serial queue anywhere in that chain, only one operation runs at a time.
But if you set operationQueue.underlyingQueue = concurrentQueue
, and do not set concurrentQueue
's target to serialQueue
, then you'll see that 64 operations run simultaneously. For operationQueue
to run operations concurrently, the entire target chain starting with its underlyingQueue
must be concurrent.
Since the main queue is always serial, NSOperationQueue.mainQueue
is effectively always serial.
In fact, if you set NSOperationQueue.mainQueue.maxConcurrentOperationCount
to anything but 1, it has no effect. If you print NSOperationQueue.mainQueue.maxConcurrentOperationCount
after trying to change it, you'll find that it's still 1. I think it would be even better if the attempt to change it raised an assertion. Silently ignoring attempts to change it is more likely to lead to confusion.
Reason 2
NSOperationQueue
submits up to maxConcurrentOperationCount
blocks to its underlyingQueue
simultaneously. Since the mainQueue.underlyingQueue
is serial, only one of those blocks can run at a time. Once those blocks are submitted, it may be too late to use the -[NSOperation cancel]
message to cancel the corresponding operations. I'm not sure; this is an implementation detail that I haven't fully explored. Anyway, if it is too late, that is unfortunate as it may lead to a waste of time and battery power.
Reason 3
As with mentioned with reason 2, NSOperationQueue
submits up to maxConcurrentOperationCount
blocks to its underlyingQueue
simultaneously. Since mainQueue.underlyingQueue
is serial, only one of those blocks can execute at a time. The other blocks, and any other resources the dispatch_queue_t
uses to track them, must sit around idly, waiting for their turns to run. This is a waste of resources. Not a big waste, but a waste nonetheless. If mainQueue.maxConcurrentOperationCount
is set to 1, it will only submit a single block to its underlyingQueue
at a time, thus preventing GCD from allocating resources uselessly.