Hide chapters

Concurrency by Tutorials

Third Edition · iOS 16 · Swift 5.7 · Xcode 14

8. Asynchronous Operations
Written by Scott Grosch

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Up to this point, your operations have been synchronous, which works very well with the Operation class’ state machine. When the operation transitions to the isReady state, the system knows that it can start searching for an available thread.

Once the scheduler has found a thread on which to run the operation, the operation will transition to the isExecuting state. At that point, your code executes and completes, and the state then becomes isFinished.

isReady isExecuting isFinished S a t chedulersearchesfor anvailablehread Y c e c ourodexecutes toompletion H - G ousekeeping etting the operation ready

How would that work with an asynchronous operation, though? When the main method of the operation executes, it will kick off your asynchronous task, and then main exits. The state of the operation can’t switch to isFinished at that point because the asynchronous method likely has yet to complete.

isReady isExecuting isFinished Asynchronous Task

Asynchronous Operations

It’s possible to wrap an asynchronous method into an operation, but it takes a bit more work on your part. You’ll need to manage the state changes manually as the operation can’t determine automatically when the task has finished executing. To make matters worse, the state properties are all read-only!

If you’re ready to throw in the towel, don’t worry. Managing the states is actually quite simple to accomplish. In fact, you will now create a base class that all asynchronous operations you use will inherit from so you never have to do it again. No, we don’t know why this class isn’t part of the framework.


In the download materials for this chapter, open AsyncAddOperation.playground in the starter folder. You can ignore the compilation error as you’ll resolve it in a moment when you add some code.

State Tracking

Since the state of an operation is read-only, you’ll first want to give yourself a way to track changes in a read-write manner, so create a State enumeration at the top of the file:

extension AsyncOperation {
  enum State: String {
    case ready, executing, finished

    fileprivate var keyPath: String {
      return "is\(rawValue.capitalized)"
var state = State.ready {
  willSet {
    willChangeValue(forKey: newValue.keyPath)
    willChangeValue(forKey: state.keyPath)
  didSet {
    didChangeValue(forKey: oldValue.keyPath)
    didChangeValue(forKey: state.keyPath)

Base Properties

Now that you have a way to track state changes and signal that a change was in fact performed, you’ll need to override the base class’ instances of those methods to use your state instead. Add these three overrides to the class:

override var isReady: Bool {
  super.isReady && state == .ready
override var isExecuting: Bool { state == .executing }
override var isFinished: Bool { state == .finished }
override var isAsynchronous: Bool { true }

Starting the Operation

All that’s left to do is implement the start method. Whether you manually execute an operation or let the operation queue do it for you, the start method is what gets called first, and then it is responsible for calling main.

override func start() {
  state = .executing

Math Is Fun!

Take a look at the rest of the code that was provided for you in the playground. Nothing should be new to you, as long as you already worked through the chapters on GCD in this book. If you run the playground with the console displayed (Shift-Command-Y), you’ll see the numbers have been added together properly.

Networked TiltShift

Time to get back to your image filtering. So far, you’ve used a hardcoded list of images. Wouldn’t it be great if the images came from the network instead? Performing a network operation is simply an asynchronous task! Now that you have a way to turn an asynchronous task into an operation, let’s get to it!


Create a new Swift file named NetworkImageOperation. You’re going to make this do more than specifically needed for the project but this way you’ll have a reusable component for any other project you work on.

import UIKit

typealias ImageOperationCompletion = ((Data?, URLResponse?, Error?) -> Void)?

final class NetworkImageOperation: AsyncOperation {
  var image: UIImage?

  private let url: URL
  private let completion: ImageOperationCompletion

init(url: URL, completion: ImageOperationCompletion = nil) {
  self.url = url
  self.completion = completion


convenience init?(string: String, completion: ImageOperationCompletion = nil) {
  guard let url = URL(string: string) else { return nil }
  self.init(url: url, completion: completion)

override func main() {
  URLSession.shared.dataTask(with: url) {
    [weak self] data, response, error in

guard let self else { return }

defer { self.state = .finished }

if let completion = self.completion {
  completion(data, response, error)

guard error == nil, let data = data else { return }

self.image = UIImage(data: data)

Using NetworkImageOperation

Head back over to TableView.swift. In order to get the list of URLs that you’ll be able to display, the starter project included a loadPhotoUrls method for you.

let op = NetworkImageOperation(url: url)
if let outputImage = op.outputImage {
if let outputImage = op.image {

Where to Go From Here?

You’ve now got reusable components for both network image downloads as well as tilt shift filtering. Wouldn’t it be nice to be able to use both at once? The next chapter will show you how to link those two together and finally provide the “aha” moment as to why you’re using operations instead of sticking with Grand Central Dispatch.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now