Concurrency in iOS: Introduction to the abstract Operation class and using its BlockOperation subclass to run tasks in parallel

[Download the full Xcode 9 project, written in Swift 4, from GitHub.]

Swift tutorials by iosbrain.comI’m going to introduce you to iOS concurrency with simple Swift 4 code that uses an API based on the Operation abstract class. In more complex scenarios, you would subclass and customize Operation, but iOS provides the built-in BlockOperation subclass of Operation which we’ll use here. I’ll review the tenets of concurrency, emphasize why it is necessary in almost all apps, explain the basic infrastructure of Operation, compare it to Grand Central Dispatch (GCD), and then walk you through the Swift 4 code I wrote to implement concurrency in a real-live app based on BlockOperation. I’ll even show you how to graphically visualize your app’s CPU and thread usage with Xcode’s Debug Navigator. Here’s the app that we’ll build together:

Press the play button if you missed the first showing

We now live in the day and age of writing apps that can run on devices with CPUs that have multiple cores. We can go way beyond the notion of “multitasking” as a bunch of processes vying for a “timeslice” of the CPU. We can literally run processes simultaneously on different cores. As iOS developers, it is vitally important that we understand the concept of concurrency. Why?


From the perspective of a user, it is easy to understand: An app’s user interface (UI) should always be responsive, no matter how many things the app is doing — and doing simultaneously, like downloading a file and performing geo-location; like a UITableViewCell's thumbnail images loading on demand as a user scrolls up and down through the UITableView.

We’ve talked about using Apple’s Grand Central Dispatch (GCD) to tackle this crucial iOS topic in my articles here and here. GCD is nice, but it’s a bit on the low-level-API end of the spectrum, and hard to fine-tune (like it’s hard to cancel a background task). Today, I’m going to start introducing you to a higher-level alternative to GCD, the Foundation framework’s family of classes headed by the abstract Operation class. We’ll do everything in Swift 4 where NSOperation has been renamed to Operation. Specifically, I’ll concentrate on showing you how to use the BlockOperation class to download a bunch of huge files in the background, allowing the sample app’s UI to remain responsive. We’ll also use BlockOperation to run a lengthy computation in the background — at the same time that those files are downloading.

Introduction

Let’s briefly refresh our memories. According to Apple, “Concurrency is the notion of multiple things happening at the same time.”

I’m not going to regurgitate the discussion on concurrency. If you’re still unclear on the concept, read my articles on “Concurrency in iOS — Grand Central Dispatch (GCD) with Swift 3″ and “Concurrency in iOS: serial and concurrent queues in Grand Central Dispatch (GCD) with Swift 3”.

Let’s concentrate on Apple’s high-level and abstract base class Operation for implementing concurrency in our iOS apps. I’m going to go minimal today by presenting you with: 1) some basic definitions, 2) the Swift 4 code for a fully functional app capable of concurrency, and 3) videos and images of my sample app in action. You should review those three aspects of my explanation of Operation and then download, build, run, and interact with my app. You might even want to set a few breakpoints. If you’ve never done much concurrent programming, if you swear by Grand Central Dispatch (GCD) as your only concurrent resource, or you’re curious about the differences between GCD and Operation, you should compare today’s discussion and code to the discussion and code in my first two articles, purposely written on GCD to show you a lower level concurrency API, “closer to the Unix kernel” if you will, here and here.

Operation

In this section, I’ll quote straight from the horse’s mouth: Apple. Note that with Swift 4, a lot of type names that used to begin with NS have lost the NS. NSOperation is now Operation; NSOperationQueue is now OperationQueue; and so on. Pages titled Operation and OperationQueue start mentioning NSOperation or NSOperationQueue as you read further into the knowledge base articles. It seems Apple is playing catch-up with the fast-paced changes it’s making to its own technologies.

  • Operation: “An abstract class that represents the code and data associated with a single task.” When you get deeper into Operation, you’ll start subclassing this guy to facilitate making your tasks capable of concurrency.
  • OperationQueue: “A queue that regulates the execution of a set of operations. … After being added to an operation queue, an Operation instance remains in that queue until it is explicitly canceled or finishes executing its task. Operations within the queue (but not yet executing) are themselves organized according to priority levels and inter-operation object dependencies and are executed accordingly. An application may create multiple operation queues and submit operations to any of them. …”

Y’all remember what a queue is, correct? I hope so, because you’re not going to get any tasks running in the background without a queue. Here are two definitions from Oxford:

British A line or sequence of people or vehicles awaiting their turn to be attended to or to proceed. …

Computing A list of data items, commands, etc., stored so as to be retrievable in a definite order, usually the order of insertion.

IMPORTANT! Instead of subclassing Operation today, we’re going to start simple with the following class:

  • BlockOperation: “An operation that manages the concurrent execution of one or more blocks. … The BlockOperation class is a concrete subclass of Operation that manages the concurrent execution of one or more blocks. You can use this object to execute several blocks at once without having to create separate operation objects for each. When executing more than one block, the operation itself is considered finished only when all blocks have finished executing.

    Blocks added to a block operation are dispatched with default priority to an appropriate work queue. …”
  • addExecutionBlock(_:): “Adds the specified block to the receiver’s list of blocks to perform. … The specified block should not make any assumptions about its execution environment. …”
  • addOperation(_:): “Adds the specified operation object to the receiver. … Once added, the specified operation remains in the queue until it finishes executing. …”
#ad

Why use Operation?

The Operation family of classes form a concurrency API that is much higher-level, abstract, and feature-full than GCD. And guess what: Operation was built using GCD. Operation provides you with the capabilities to:

  • cancel a task or tasks;
  • monitor the states of tasks (like through isReady, isExecuting, isFinished, or isCancelled);
  • create dependencies between tasks, for example, so you can specify a specific order in which your tasks will execute in the background;
  • specify whether tasks running synchronously or asynchronously (you could do that in GCD);
  • not grow grey hairs over synchronization as you have “multicore aware[ness];” and,
  • “prioritize the order in which operations [tasks] execute.”

Demonstrating Operation through BlockOperation

Remember, I’m just introducing you to the whole Operation hoo-hah today. It’s a big API and takes time, work, and a decent understanding of the technology to get a fully-featured Operation-based app up and running in which you can cancel tasks, observe their states, prioritize tasks, specify synchronous vs. asynchronous execution, etc. That’s why I’m starting today with BlockOperation, an “operation that manages the concurrent execution of one or more blocks” and which is a “concrete subclass of Operation that manages the concurrent execution of one or more blocks.”

Note that my BlockOperation sample app code, shown below and downloadable here, looks an awful lot like the GCD code that I introduced you to in my article, “Concurrency in iOS: serial and concurrent queues in Grand Central Dispatch (GCD) with Swift 3”. That’s the whole idea. I want to gently introduce you into the wild world of Operation.

When reading my code, and especially when running it, notice that the images I download always come down in a different order. The ordering of my download tasks is asynchronous. I’ll explain why later, but as homework, can you figure out why? Hint: We’d have to get into subclassing the Operation class, which is generally what people with more-than-trivial concurrency needs do. I didn’t want to do that here. I wanted to show you that you can write some basic code for backgrounding tasks using just BlockOperation when your needs are simple. Hint, hint: With Operation, the default is asynchronous.

The code

Here’s my code demonstrating BlockOperation in action. Please read the inline commentary as that’s where I explain a lot of this code:


#ad

The app in action
Here’s my app downloading files asynchronously (video immediately below). Notice that my app’s UI stays fully responsive while images are downloaded in the background? I can keep pressing the “Increment count:” button and the UIActivityIndicatorView keeps spinning all while images are being downloaded and a long-running calculation is being executed.

Press the play button if you missed the first showing

Console output from my code – downloading files
Here’s the console output when I download images:

Note that the download image tasks are queued for background execution almost instantly and in order from imageURLs array index 0 to 10 (11 items). Note that the images download asynchronously.

Here’s a video of the console output from a different run of the app. It should reinforce the text version of my console output above and should reinforce my explanation of that output:

Press the play button if you missed the first showing

Console output from my code – downloading files and calculating
Here’s the console output from my app downloading files asynchronously — and performing a long-running calculation simultaneously:

Examining my app’s CPU usage

If you bring up Xcode’s Debug Navigator and click on CPU, you can see a visual representation of how hard (or easy) your app is being on your app target device’s CPU. You can see your main thread (1) and how many other threads iOS has decided are necessary for your app to perform well. Note that as soon as I submitted my 11 download image tasks — and my processor-intensive arithmetic task — to OperationQueues, the number of threads increased almost immediately. The number of threads does not necessarily equal the number of tasks I submit. It’s up to iOS to optimize the details. If you’re patient and watch the threading details until after the images are downloaded and the calculation finishes, you’ll see the extra threads get cleaned up. This is so cool.

I’m not going into great depth here. Just note that you have another tool in your arsenal for tuning, examining, and debugging your apps.

Press the play button if you missed the first showing

Wrapping up

Do you feel you have a sense for Operation and a simple demonstration of its usefulness with its subclass BlockOperation? Remember, my purpose was not to throw you into all the complexities of full-blown
concurrency programming with Operation. My purpose was to introduce the topic, show you that Operation is built on the shoulders of GCD, and give you a teaser as to the many possibilities — and control — afforded to you by using Operation instead of GCD.

#ad

Author: Andrew Jaffee

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by https://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.

Leave a Reply

Your email address will not be published. Required fields are marked *