iOS: Operations (NSOperations) — why again?
GCD or Operations
Every time I ask about the differences between these 2 approaches to queue tasks I’m getting similar responses:
- use GCD in fire-and-forget
- use GCD for simple, single asynchronous calls
but use Operations when
- you want to cancel them
- you want to execute more tasks in a one go
- when you want to execute them in dependencies
- when you want to prioritise their execution
These recommendations sound like GCD would lack some of the core features of Operations, yet it’s being said repeatedly Operations are more or less wrapper around GCD.
Well, the latter is certainly right and there seem to be nothing special with the Operations, what you couldn’t do with the GCD, actually, many things in an easier way, more readable, although the personal bias could play a role as it is with everything in our lives. I could say to the certain degree, that Operations did not fully achieve the task of simplyfing by encapsulation, because at least for my taste, too many things seem to be easier to implement just by using the GDC.
Misconceptions
However, I believe there are some real misconceptions about Operations which should be mentioned:
1. Cancelling the app doesn’t just happen. A developer must implement cancellation and setup KVC on a property to achieve that, OperationQueue
just listens to it. Cancellation needs to be implemented same as you would do it in GCD as well. No magic here! If you need to cancel, for example, an asynchronous URLSession
, you will have to use KVC to change the status to notify OperationQueue
, but you will still have to implement cancelling the URLSession
yourself. Actually, you have additional work with Operations here.
2. Group of tasks can be atomically executed easily with DispatchGroup
as well, it’s not something that OperationQueues
are capable alone, actually, it’s shorter, easier and leaner with the GCD.
3. Dependencies again, don’t just happen out of the box, when using OperationQueue
. Operations need to be linked as dependent beforehand and all overhead of isFinished and the rest of the boilerplate needs to be implemented so that this mechanism work. I would argue, that using Dispatch
on serial queue with GCD is more readable and easier to understand, especially if there are more consequent tasks in question.
However, if the tasks to be synchronised are themselves asynchronous by nature, then GCD becomes even much easier to use.
If there’s only one additional dependency, then we can nest the second task.
If there are more, then using DispatchGroups is a very easy thing to do…
There’s much more muscle to show, when dependencies could be between single taks and group of tasks and this is where GCD excels compared to Operations in terms of simplicity. Still, this is not the use case that would happen very often, so, not really a huge advantage.
Downsides
But here are some real downsides IMHO to using the Operations
and I don’t want to repeat the overhead of boilerplate coding which I believe is the only thing most of the people mention when comparing the approaches. There are more important things to be mentioned here:
Operations
areNSOperations
and are bringing dynamic dispatch to all the subclasses. There is no Swift counterpart yet for it and there is a reason to believe there won’t be because of the next few things mentioned here- they use KVC and KVO, which are again unknown concepts in Swift and highly unlikely to be implemented anytime soon if ever.
- we don’t have a stateless return of
Operation
result since thecompletionBlock
ofOperation
is void. Consequently, we need to create a readable properties (state) on anOperation
, just to pass back the result. Any kind of additional state inducts additional memory management complexity Operations
can lure developers to use them „uniformly“ even for the trivial tasks, wrapping simple code to “follow the approach”…- A bit more complexity in Unit tests, some helper
OperationQueue
mocks need to be made to be able to test it properly - Sometimes they just become a dump for all kinds of business logic, violating almost all SOLID principles. If anything,
Operations
should be mostly boilerplate with just 1+ method call and 1+ result property, IMHO.
Also when watching the WWDC 2015 video, I couldn’t find any key feature, where Operations would be irreplacable. Of course, they are a valid concept and sometimes a list of tasks, needed to be executed atomically can be nicely encapsulated in it, but in cases like that on the client, we should be asking if something is wrong with our API architecture itself and encapsulate complexity on the server side instead.
I created a very small repository and made comparison between 3 most common approaches of executing asynchronous tasks.