3

Is it safe to add an NSOperationQueue to an NSOperation, and then add this operation to another NSOperationQueue?

Here is some code to visualize what I am trying to do.

NSOperationQueue *mainQueue = [NSOperationQueue alloc] init];

// Here I declare some NSBlockOperation's, i.e. parseOperation1-2-3
// and also another operation called zipOperation, which includes
// an NSOperationQueue itself. This queue takes the processed (parsed) files
// and write them to a single zip file. Each operation's job is to write the data
// stream and add it to the zip file. After all operations are done,
// it closes the zip.

[zipOperation addDependency:parseOperation1];
[zipOperation addDependency:parseOperation2];
[zipOperation addDependency:parseOperation3];

[mainQueue addOperation:parseOperation1];
[mainQueue addOperation:parseOperation2];
[mainQueue addOperation:parseOperation3];
[mainQueue addOperation:zipOperation];
Girish
  • 4,692
  • 4
  • 35
  • 55
tolgamorf
  • 809
  • 6
  • 23
  • Are you asking whether you can create another `NSOperationQueue` from within an `NSOperation`? Yes, you can. – Rob May 25 '13 at 10:47
  • Yes, but I am also asking if it is a good approach or does it have some consequences? – tolgamorf May 25 '13 at 10:50
  • Personally, when I need multiple queues, I'll create queues for those specific purposes in advance (e.g. image processing queue, network queue, etc.). I wouldn't generally create a queue on the fly like that. The risk of the above model is that you could, if you fired off too many of these operations that created separate queues is that you could end up using up all of your worker threads. You haven't even shared your logic for why you want to create the separate queue in the first place, so we can't comment on whether that makes sense at all. – Rob May 25 '13 at 10:57
  • 1
    By the way, you might want to rethink the variable name, `mainQueue`. That term generally refers to that main queue of the app, where all of the UI stuff takes place. There is even a `NSOperationQueue` class method called [`mainQueue`](http://developer.apple.com/library/ios/documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html#//apple_ref/doc/uid/TP40004592-RH2-SW21), which gets the main queue of the app. This variable name can only be a source of confusion in the future. – Rob May 25 '13 at 11:00
  • 1
    The name of the queues and operations above are not the actual ones that I use in my code. I just used them in the question for simplicity. Thank you for the heads up though, I really appreciate it. – tolgamorf May 25 '13 at 11:08

1 Answers1

5

I have used this approach and have it running in live code deployed on the App Store. I haven't experienced any issues during development or in the last 2 months since the code has been live.

In my case I had a high level series of operations, some of which contained a set of sub operations. Rather than expose the detail of each sub operation into the high level code, I created NSOperations which themselves contained NSOperationQueues and enqueued their own sub operations. The code I ended up with was much cleaner and easier to maintain.

I read extensively into NSOperation and have not seen any commentary that warns against this approach. I reviewed a lot of information online, the Apple documentation, and WWDC videos.

The only possible "drawback" might be the added complexity of understanding and implementing a Concurrent operation. Embedding an NSOperationQueue in an NSOperation means that operation becomes Concurrent.

So that's a 'YES' from me.


Additional details about concurrent operations:

An NSOperationQueue calls the start method on a normal (non-concurrent) NSOperation and expects the operation to be finished by the time the start call returns. For instance some piece of code you supplied to NSBlockOperation is complete at the end of the block.

If the work will not be finished by the time the start call returns then you configure the NSOperation as a Concurrent operation, so the NSOperationQueue knows that it has to wait until you tell it that the operation is finished at some later point in time.

For example, concurrent operations are often used to run asynchronous network calls; the start method only starts the network call, which then runs in the background, and calls back to the operation when its finished. You then change the isFinished property of the NSOperation to flag that the work is now complete.

So.... Normally when you add operations to an NSOperationQueue that queue runs those operations in the background. So if you put an NSOperationQueue inside an NSOperation then that operations work will be done in the background. Therefore the operation is concurrent and you need to flag when the internal NSOperationQueue has finished processing all it's operations.

Alternatively there are some methods on NSOperationQueue such as waitUntilAllOperationsAreFinished which could be used to ensure all the work was done before the start call returns, however these involve blocking threads and I avoided them, you may feel more comfortable with that approach, and making sure you don't have any side effects from blocking threads.

In my case I was already familiar with Concurrent operations so it was straightforward just to set it up as a Concurrent operation.

Some documentation about concurrent operations:

Concurrency Programming Guide: Configuring Operations for Concurrent Execution

In this example they are detaching a thread to perform work in the background, in our case we would be starting the NSOperationQueue here.

Rory O'Bryan
  • 1,894
  • 14
  • 22
  • Thank you for your answer. I preferred using `NSBlockOperation`'s instead of subclassing `NSOperation`. Some of my operations are one-liner only and I thought it is not necessary to subclass. – tolgamorf May 27 '13 at 05:12
  • Yes, I did the same, some operations are block operations, some are subclasses depending on their requirements. The operation with the NSOperationQueue inside though, is a subclass because it has to manage the queue it contains and also be setup as a concurrent operation. Perhaps I misunderstood your question? – Rory O'Bryan May 27 '13 at 06:01
  • No, your answer satisfies my question. I haven't understood your last paragraph (about concurrency) though, could you elaborate please? – tolgamorf May 27 '13 at 06:23
  • 1
    Added some text about concurrent operations, hope it makes sense. – Rory O'Bryan May 27 '13 at 10:08
  • Thanks for the much detailed explanation, yeah it makes sense now. – tolgamorf May 27 '13 at 10:59
  • By the way, look at the [NSOperation class reference](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html) I just saw a note: `Note: In OS X v10.6, operation queues ignore the value returned by isConcurrent and always call the start method of your operation from a separate thread. In OS X v10.5, however, operation queues create a thread only if isConcurrent returns NO. In general, if you are always using operations with an operation queue, there is no reason to make them concurrent.` – tolgamorf May 27 '13 at 11:23
  • So this case isn't the general case because we are embedding an operation queue inside an operation. The first part of the quote is clearer if you remove the words "ignore the value returned by isConcurrent and". It's not ignoring `isConcurrent` it's just ignoring it when deciding whether to create a separate thread to call start. It's definitely not the case `isConcurrent` is ignored in general. – Rory O'Bryan May 27 '13 at 11:44
  • There was a similar change on iOS as well which caused a common issue detailed in QA1712. I guess this note is warning OS X users of the same change. – Rory O'Bryan May 27 '13 at 12:05
  • @RoryO'Bryan Rory, can you add `NSOperation` subclass code here or somewhere to see the example. I'm not sure what to do in `main` because I add my "sub" operations to `NSOperationQueue` in init method of my `NSOperation` subclass. – Dima Deplov May 04 '16 at 12:26