Advertisements
Everything about iOS app development

Operation vs Completion block madness

Operation and OperationQueue classes are simply put …wait for it… AWESOME. I’ve been writing bugs into iOS apps since 7+ years and looking back I’ve made a lot of mistakes, lot of bad projects, and lot of “GodViewControllers” /if you know what I mean/.

I’ve recently started using Operations all over my projects. To give you some example:

  1.  when using networking calls
  2. when parsing objects
  3. when displaying UI elements (e.g.: UICollectionView)
  4. handling files and folders
  5. handling serial calls
  6. etc.

As you can see there are a ton of ways you can and should use Operations. But let me give you some real examples.

Example #1 – Downloading network data, parsing the data and saving it to disk

You’ll be like: /why should I create 2 operations for this? when I can just use separate methods inside my networking class and call it a day./ – Johnny
Yeah Johnny, you could do that, but wouldn’t it be nicer if you could control all those blocks of code? Let’s say you start downloading the data, than you parse it and returns the parsed data to your UIViewController to display it, while in the background you store the data. And with operations you can simply do that.
Let’s have one operation for downloading + parsing the data, another one for returning to the UI with the parsed objects, and another one that stores that data in the background.

So at the end of the day, you can edit these actions separately, you’ll have more control over errors and once you receive a new feature request from your PM – that after storing the data locally, you should also call another endpoint to fetch new data – you’ll be relaxed, cause you’ll know you can handle it easily with another operation.

In the old days, you would do something like this:

As you can see, that’s a madness, and it doesn’t even handle failures. What if you return an optional object from the parseData method, as you might have downloaded data that is invalid to you, or it is nil, you need to catch that and do that for all the methods that handles failures. You’ll end up going crazy with all the if else conditions. Not nice!

Example #2 – Downloading data from an endpoint, than calling another endpoint after it finished

 

You have been there before, haven’t you? Your PM (project manager) told you, when the app downloads the available stores nearby the users’ location, it should also fetch the news feed from the API right after the first fetch finishes. You, again, do the same thing, call the fetchStores method, and in its completion block you call the fetchNews method. You’ll end up in a same problem as before.

The solution – Operations

As I said, you need to use Operations, single operation for each task you want to do. Take Example #2.
  1. One operation for fetching data
  2. One operation for parsing data
  3. One operation for storing the data
It will make your code cleaner and easier to maintain later on. Trust me.
Let’s see how that is in real life. The heart of the whole thing is the AbstractOperation class, this is the class you want to use as your base class for your new operations.
It looks like this:

As you can see, nothing fancy, just overwriting some functions and variables, so you can control them better.

You can check out my tutorial about uploading content to Firebase, this could be also optimized with operations.

How do I create my own Operation?

Simply subclass AbstractOperation class and implement the execute() method. The start method will be called when you add your operation to an OperationQueue and execute that queue. So ideally in your execute() (function, I should say), you implement whatever that operation should do, whether to download data, parse it, store it or else.

Let’s say you create a new Operation and call it: StoresDownloadOperation. It is an operation, that would download stores nearby the user’s location. In that operation, you have a function that starts downloading the data, and when it finished it just calls the finished(error: String?) function. When the finished(error: String?) function is called, it means your operation is done (finished) and can be removed from your queue if it is added to any.

Let’s see the example:

That’s all, what we do here is: initialise the Operation with a location (of the user), override the execute() function where we can start downloading the data, once that is finished, we “kill” the operation by calling the finished(error: String?) function.

How to call an operation?

There are 2 ways, inside an OperationQueue or without one.

1. Inside an OperationQueue

For that you obviously need an operationQueue. “Captain Obvious for the rescue!“
So let’s initialise one:

That’s all, the queue will start and the operation will begin to execute. But, as you can see, there is a problem. The operation downloads the data, but how do we get it? Hmm, tricky. You’ll need to call the completionBlock of the storesDownloadOperation for that. So the above snippet would look like this:

How awesome is that? Pretty amazing if you ask me.

2. Without an OperationQueue

Without a queue you just simply initialise the operation and call the execute() function.

I hope it is clear how awesome these things are by now.

What if I have multiple operations, like in your examples?

So you have the StoresDownloadOperation, what if you also have one for filtering those stores, how would you combine the two?

So let’s keep it simple, your filter operation will have a function that expects an array of Store objects, and filter it in its own way, so the method would look like so in its execute() function:

filteredStores would be a variable with all the filter stores inside. Let’s call this operation: FilterStoresOperation.

Now we have both classes, let’s connect them and finish this tutorial, shall we?

We would need an OperationQueue that would execute the operations 1-by-1, so that the filter waits until the we have downloaded the stores and only than would that start executing.

So, as you can see, now we have a queue with 2 operations. One that does the download part and another one that filters the downloaded data based on whatever filter we want.
But there is one thing missing, the update of the table/collectionView.
For that we just need to define the storesFilterOperation’s completionBlock and do the update there, like this:

There, with that in place, we can download data from the cloud, filter it how we want and display it in the collectionView. Isn’t it awesome? Let me tell you, it is.

I hope you enjoyed this tutorial. Share your thoughts in the comments below where you think it would help your iOS app? Just like in this login app where you could combine the login action and displaying another viewController into an operation queue.

Advertisements
%d bloggers like this: