skip to Main Content
Use Operations And Operationqueues Swift Xcode Ios Developer Squashed

Operations vs Completion block madness

If you are not using Operations, you my friend are missing out big time. Operations 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 refreshing 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 nil, you need to catch that and do it 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 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 of 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.

How do I create my own Operation?

Simply subclass AbstractOperation class and override the execute() method. This method will be called when you add your operation to an OperationQueue and execute it.

So ideally in your execute() function, you implement whatever that operation should do, whether it to be downloading data, parsing it, or else.

Let’s say you create a new Operation and call it: StoresDownloadOperation.

It is an operation, that would download the nearby stores around 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 with an error message if any. When this 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 start function where we start downloading the data, once that function is finished, we “kill” the operation by calling the finished(error: String?)  function.

How to call the 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 start() function.

I hope it clear now, how awesome these things are.

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, if you did, make sure to share it with a friend, colleague.

If you are still not convinced, make sure to watch this video and check the source code.

 

Next up, we’ll update all of our templates to use Operations and get better in downloading, handling data, first template will be our Car Dealer app.