NSOperation vs Grand Central Dispatch
Asked Answered
H

9

492

I'm learning about concurrent programming for iOS. So far I've read about NSOperation/NSOperationQueue and GCD. What are the reasons for using NSOperationQueue over GCD and vice versa?

Sounds like both GCD and NSOperationQueue abstract away the explicit creation of NSThreads from the user. However the relationship between the two approaches isn't clear to me so any feedback to appreciated!

Huonghupeh answered 29/4, 2012 at 15:24 Comment(3)
+1 for good question - curious on the results. So far, I just read that GCD can easily be dispatched across CPU cores, rendering it to be the "new hot shit".Simasimah
Some related discussion can be found in this question: Why should I choose GCD over NSOperation and blocks for high-level applications?Linalinacre
cocoacasts.com/…Hardcore
M
544

GCD is a low-level C-based API that enables very simple use of a task-based concurrency model. NSOperation and NSOperationQueue are Objective-C classes that do a similar thing. NSOperation was introduced first, but as of 10.5 and iOS 2, NSOperationQueue and friends are internally implemented using GCD.

In general, you should use the highest level of abstraction that suits your needs. This means that you should usually use NSOperationQueue instead of GCD, unless you need to do something that NSOperationQueue doesn't support.

Note that NSOperationQueue isn't a "dumbed-down" version of GCD; in fact, there are many things that you can do very simply with NSOperationQueue that take a lot of work with pure GCD. (Examples: bandwidth-constrained queues that only run N operations at a time; establishing dependencies between operations. Both very simple with NSOperation, very difficult with GCD.) Apple's done the hard work of leveraging GCD to create a very nice object-friendly API with NSOperation. Take advantage of their work unless you have a reason not to.

Caveat: On the other hand, if you really just need to send off a block, and don't need any of the additional functionality that NSOperationQueue provides, there's nothing wrong with using GCD. Just be sure it's the right tool for the job.

Mahan answered 29/4, 2012 at 20:16 Comment(4)
NSOperation to be specific an abstract class.Erdda
@Sandy It's actually the opposite, GCD is used by NSOperation (at least in later versions of iOS and OS X).Komsomolsk
@BJ Homer We can add task in serial dispatch queue to acheive depeancy. so justfy how operation queue have advantage over thatBiz
@RajAggrawal Yes, that works… but then you're stuck with a serial queue. NSOperation can do "execute this operation after those other three are done, but concurrently with all the other stuff going on." Operation dependencies can even exist between operations on different queues. Most people won't need that, but if you do, NSOperation would be a better choice.Mahan
L
382

In line with my answer to a related question, I'm going to disagree with BJ and suggest you first look at GCD over NSOperation / NSOperationQueue, unless the latter provides something you need that GCD doesn't.

Before GCD, I used a lot of NSOperations / NSOperationQueues within my applications for managing concurrency. However, since I started using GCD on a regular basis, I've almost entirely replaced NSOperations and NSOperationQueues with blocks and dispatch queues. This has come from how I've used both technologies in practice, and from the profiling I've performed on them.

First, there is a nontrivial amount of overhead when using NSOperations and NSOperationQueues. These are Cocoa objects, and they need to be allocated and deallocated. In an iOS application that I wrote which renders a 3-D scene at 60 FPS, I was using NSOperations to encapsulate each rendered frame. When I profiled this, the creation and teardown of these NSOperations was accounting for a significant portion of the CPU cycles in the running application, and was slowing things down. I replaced these with simple blocks and a GCD serial queue, and that overhead disappeared, leading to noticeably better rendering performance. This wasn't the only place where I noticed overhead from using NSOperations, and I've seen this on both Mac and iOS.

Second, there's an elegance to block-based dispatch code that is hard to match when using NSOperations. It's so incredibly convenient to wrap a few lines of code in a block and dispatch it to be performed on a serial or concurrent queue, where creating a custom NSOperation or NSInvocationOperation to do this requires a lot more supporting code. I know that you can use an NSBlockOperation, but you might as well be dispatching something to GCD then. Wrapping this code in blocks inline with related processing in your application leads in my opinion to better code organization than having separate methods or custom NSOperations which encapsulate these tasks.

NSOperations and NSOperationQueues still have very good uses. GCD has no real concept of dependencies, where NSOperationQueues can set up pretty complex dependency graphs. I use NSOperationQueues for this in a handful of cases.

Overall, while I usually advocate for using the highest level of abstraction that accomplishes the task, this is one case where I argue for the lower-level API of GCD. Among the iOS and Mac developers I've talked with about this, the vast majority choose to use GCD over NSOperations unless they are targeting OS versions without support for it (those before iOS 4.0 and Snow Leopard).

Linalinacre answered 30/4, 2012 at 2:4 Comment(18)
I only mildly disagree; I use plain GCD quite a bit. But I think you discount NSBlockOperation too heavily in this answer. All the benefits of NSOperationQueue (dependencies, debugability, etc.) apply to block operations too.Mahan
@BJHomer - I think the avoidance of NSBlockOperation is more a matter of personal preference in my case, although I have shied away from NSOperations in general after seeing the overhead from their use drag down a couple of applications. If I'm going to use blocks, I tend to go all-in on GCD, with the rare exception of when I need dependency support.Linalinacre
+1, thanks for this analysis. Apple seems to be advocating both (like WWDC 2012's session on concurrent UI), so this is much appreciated.Forgiveness
Since you have used GCD pretty extensively I have a specific query about it. I am making a game using Cocos2d and every UI operation needs to be in main queue. Do you think GCD is low enough overhead to allow me to dispatch for each UI update or should I shy away from too many dispatches during background updates?Mendelian
@VolureDarkAngel - GCD is extremely fast at handling dispatches like that. It shouldn't be your bottleneck in a situation like you describe, unless you somehow back up a pile of updates into a queue due to slow I/O accesses or something of the sort. That's probably not the case here, though.Linalinacre
My experience has not been slowness but Black Box Artifacts in game where updates was off main queue. Just wanted to see what the overhead was for GCD and this was the most meaningful post I have seen about it. the only other overhead I have is a method to ensure its not dispatched multiple times. If you have time could you give me your feedback on it and thoughts on if its a good route ? here is the code pastebin.com/Z7gJ4nMz I appreciate any thoughts on it. Also, feel free to use it in your projects if you like it.Mendelian
@VolureDarkAngel - If you need to guarantee that no more than one block of a given action is in the queue at a time, you can use a dispatch semaphore like I describe here: https://mcmap.net/q/75407/-using-grand-central-dispatch-how-can-i-check-if-there-is-a-block-already-running . I use this for firing off frame render actions at 60 FPS in a couple iOS applications. Semaphores are extremely lightweight.Linalinacre
That block was more to ensure that it did not dispatch if it was already on the queue in question. Essentially I figure if im on the main queue, It should not try to dispatch, rather it should just call the block directly. Do you think it is a good direction or overkill ?Mendelian
@VolureDarkAngel - You do need something like this as a helper function if you're doing a synchronous dispatch, because otherwise a synchronous dispatch to the main queue from the main queue causes a deadlock. I have a fairly lightweight function that I use for this in many projects: https://mcmap.net/q/75408/-how-to-dispatch-on-main-queue-synchronously-without-a-deadlock . That would probably be faster and have less overhead than your method described above.Linalinacre
Sounds good, I have a lot more functions in that class so Ill likely keep using it but Will look into difference between [NSThread isMainThread] and dispatch_get_main_queue() == dispatch_get_current_queue(). Aside from that I really appreciate your time. I have only been coding iOS for a few years and began watching your iTunesU course videos and have checked out your molecules code at Sunset Lake Software. Thank you for taking time out and for moderating this site. Your ability to describe without confusion is remarkable.Mendelian
@BradLarson I realize I'm late at seeing this, but I've just started using NSOperationQueue/GCD. Do you have any timing tests that you can show? If not, I'm happy to run my own as well.Quillet
@MikeWelsh - I don't have any specific benchmarks on this, but I do describe one practical use case in my answer that brought out a significant performance difference between the two approaches. I noticed the same overhead in other cases, which went away when I switched to GCD. It shouldn't be that hard to concoct a synthetic benchmark if you want to see this for yourself.Linalinacre
1) nowadays GCD objects are Cocoa objects too(for the sake of ARC) 2) dependencies can be manages through dispatch_group*Female
Can you please give an example of a dependency?Metaplasia
@asma22 - It's common to have calculations that can be done in chunks, but the final calculation of one stage may need the results from several previous stages. In that case, you can make that later operation depend on the earlier operations, and the scheduling will be managed such that those all complete before the last one runs.Linalinacre
@BradLarson - I will have to agree with you. I am testing the usage of CIFilters over a stream from AVFoundation camera and I was using NSOperations to execute filter operations. CPU usage was under 74% with a specific filter. After replacing the queue with GCD, CPU usage dropped 20% with the same filter! Probably the overhead to create the block that was being executed as NSOperation.Bestialize
This is surely very late, but what do you mean by Blocks here? I'm new to iOS, I've only seen BlockOperationBonne
@Dennis - Objective-C blocks: developer.apple.com/library/archive/documentation/Cocoa/… , aka closures. At the time this was written, they were still a relatively new Objective-C language feature, but they've been a core element of Swift from the beginning.Linalinacre
A
131

GCD is a low-level C-based API.
NSOperation and NSOperationQueue are Objective-C classes.
NSOperationQueue is objective C wrapper over GCD. If you are using NSOperation, then you are implicitly using Grand Central Dispatch.

GCD advantage over NSOperation:
i. implementation
For GCD implementation is very light-weight
NSOperationQueue is complex and heavy-weight

NSOperation advantages over GCD:

i. Control On Operation
you can Pause, Cancel, Resume an NSOperation

ii. Dependencies
you can set up a dependency between two NSOperations
operation will not started until all of its dependencies return true for finished.

iii. State of Operation
can monitor the state of an operation or operation queue. ready ,executing or finished

iv. Max Number of Operation
you can specify the maximum number of queued operations that can run simultaneously

When to Go for GCD or NSOperation
when you want more control over queue (all above mentioned) use NSOperation and for simple cases where you want less overhead (you just want to do some work "into the background" with very little additional work) use GCD

ref:
https://cocoacasts.com/choosing-between-nsoperation-and-grand-central-dispatch/ http://iosinfopot.blogspot.in/2015/08/nsthread-vs-gcd-vs-nsoperationqueue.html http://nshipster.com/nsoperation/

Aghast answered 15/10, 2016 at 21:36 Comment(5)
As said, Max number of Operation can be specified in NSOperationQueue, Then what can be the maximum number of operation(dispatch queues) in GCD ? Suppose I have a project, Then how many operation(dispatch queues) can I do. or their is any maximum limits upto which we can do.Nameless
It depends on the system conditions here is detailed info : #14996301Aghast
We can cancel task in GCD too using DispatchWorkItem and we can suspend and resume alsoBegotten
@Ankitgarg Calling cancel on DispatchWorkItem will stop tasks from executing if they have yet to be run, but won't halt something that's already executing. and how do you pause/resume a DispatchWorkItem??Converted
This is simple and nice way of explanation.Lylelyles
T
34

Another reason to prefer NSOperation over GCD is the cancelation mechanism of NSOperation. For example, an App like 500px that shows dozens of photos, use NSOperation we can cancel requests of invisible image cells when we scroll table view or collection view, this can greatly improve App performance and reduce memory footprint. GCD can't easily support this.

Also with NSOperation, KVO can be possible.

Here is an article from Eschaton which is worth reading.

Tierratiersten answered 2/1, 2014 at 8:8 Comment(2)
It is worth noting that if what you are canceling is the network operation of loading the image, then you do not need NSOperation for this, as NSURLSessionTask.cancel and NSURLSession.invalidateAndCancel provide this functionality. In general, NSURLSession provides some of the functionality of an NSOperationQueue, as NSURLSessionTask provides some of the functionality of an NSOperationAlrich
@Alrich As explained here (#21919222), it seems that NSURLSession uses NSOperationQueue as a building block.Bronwen
B
33

GCD is indeed lower-level than NSOperationQueue, its major advantage is that its implementation is very light-weight and focused on lock-free algorithms and performance.

NSOperationQueue does provide facilities that are not available in GCD, but they come at non-trivial cost, the implementation of NSOperationQueue is complex and heavy-weight, involves a lot of locking, and uses GCD internally only in a very minimal fashion.

If you need the facilities provided by NSOperationQueue by all means use it, but if GCD is sufficient for your needs, I would recommend using it directly for better performance, significantly lower CPU and power cost and more flexibility.

Bernabernadene answered 30/4, 2012 at 0:50 Comment(0)
P
24

Both NSQueueOperations and GCD allow executing heavy computation task in the background on separate threads by freeing the UI Application Main Tread.

Well, based previous post we see NSOperations has addDependency so that you can queue your operation one after another sequentially.

But I also read about GCD serial Queues you can create run your operations in the queue using dispatch_queue_create. This will allow running a set of operations one after another in a sequential manner.

NSQueueOperation Advantages over GCD:

  1. It allows to add dependency and allows you to remove dependency so for one transaction you can run sequential using dependency and for other transaction run concurrently while GCD doesn't allow to run this way.

  2. It is easy to cancel an operation if it is in the queue it can be stopped if it is running.

  3. You can define the maximum number of concurrent operations.

  4. You can suspend operation which they are in Queue

  5. You can find how many pending operations are there in queue.

Parliament answered 14/8, 2014 at 20:45 Comment(0)
C
6

GCD is very easy to use - if you want to do something in the background, all you need to do is write the code and dispatch it on a background queue. Doing the same thing with NSOperation is a lot of additional work.

The advantage of NSOperation is that (a) you have a real object that you can send messages to, and (b) that you can cancel an NSOperation. That's not trivial. You need to subclass NSOperation, you have to write your code correctly so that cancellation and correctly finishing a task both work correctly. So for simple things you use GCD, and for more complicated things you create a subclass of NSOperation. (There are subclasses NSInvocationOperation and NSBlockOperation, but everything they do is easier done with GCD, so there is no good reason to use them).

Campanology answered 6/8, 2014 at 0:41 Comment(0)
T
3

Well, NSOperations are simply an API built on top of Grand Central Dispatch. So when you’re using NSOperations, you’re really still using Grand Central Dispatch. It’s just that NSOperations give you some fancy features that you might like. You can make some operations dependent on other operations, reorder queues after you sumbit items, and other things like that. In fact, ImageGrabber is already using NSOperations and operation queues! ASIHTTPRequest uses them under the hood, and you can configure the operation queue it uses for different behavior if you’d like. So which should you use? Whichever makes sense for your app. For this app it’s pretty simple so we just used Grand Central Dispatch directly, no need for the fancy features of NSOperation. But if you need them for your app, feel free to use it!

Titmouse answered 13/8, 2014 at 11:40 Comment(0)
I
0

I agree with @Sangram and other answers but want to add few points. Correct me if I am wrong.

I think now a days first two points of @Sangram's answer are not valid (i. Control On Operation ii. Dependencies). We can achieve these two by using GCD also. Trying to explain by code(do not focus on quality of code, this is for reference purpose only)

func methodsOfGCD() {
    
    let concurrentQueue = DispatchQueue.init(label: "MyQueue", qos: .background, attributes: .concurrent)
    
    
    //We can suspend and resume Like this
    concurrentQueue.suspend()
    concurrentQueue.resume()
    
    //We can cancel using DispatchWorkItem
    let workItem = DispatchWorkItem {
        print("Do something")
    }
    concurrentQueue.async(execute: workItem)
    workItem.cancel()
    
    //Cam add dependency like this.
    //Operation 1
    concurrentQueue.async(flags: .barrier) {
        print("Operation1")
    }

    //Operation 2
    concurrentQueue.async(flags: .barrier) {
        print("Operation2")
    }

    //Operation 3.
    //Operation 3 have dependency on Operation1 and Operation2. Once 1 and 2 will finish will execute Operation 3. Here operation queue work as a serial queue.
    concurrentQueue.async(flags: .barrier) {
        print("Operation3")

    }

}
Impearl answered 3/10, 2021 at 13:30 Comment(1)
maybe you can explain let workItem = DispatchWorkItem when done with objc.Avigation

© 2022 - 2024 — McMap. All rights reserved.