Home iOS & Swift Books Concurrency by Tutorials

6
Operations Written by Scott Grosch

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Now that you’re a ninja master of Grand Central Dispatch, it’s time to shift gears and take a look at operations. In some regards, operations act very much like GCD, and it can be confusing as to the difference when you first start utilizing concurrency.

Both GCD and operations allow you to submit a chunk of code that should be run on a separate thread; however, operations allow for greater control over the submitted task.

As mentioned at the start of the book, operations are built on top of GCD. They add extra features such as dependencies on other operations, the ability to cancel the running operation, and an object-oriented model to support more complex requirements.

Reusability

One of the first reasons you’ll likely want to create an Operation is for reusability. If you’ve got a simple “fire and forget” task, then GCD is likely all you’ll need.

An Operation is an actual Swift object, meaning you can pass inputs to set up the task, implement helper methods, etc. Thus, you can wrap up a unit of work, or task, and execute it sometime in the future, and then easily submit that unit of work more than once.

Operation states

An operation has a state machine that represents its lifecycle. There are several possible states that occur at various parts of this lifecycle:

BlockOperation

You can quickly create an Operation out of a block of code using the BlockOperation class. Normally, you would simply pass a closure to its initializer:

let operation = BlockOperation {
  print("2 + 3 = \(2 + 3)")
}

Multiple block operations

In the starter materials for this chapter, you’ll find a playground named BlockOperation.playground. This playground provides a default duration function for timing your code, which you’ll use in a moment.

let sentence = "Ray’s courses are the best!"
let wordOperation = BlockOperation()

for word in sentence.split(separator: " ") {
  wordOperation.addExecutionBlock {
    print(word)
  }
}

wordOperation.start()

sleep(2)
duration {
  wordOperation.start()
}

wordOperation.completionBlock = {
  print("Thank you for your patronage!")
}

Subclassing operation

The BlockOperation class is great for simple tasks but if performing more complex work, or for reusable components, you’ll want to subclass Operation yourself.

Tilt shift the wrong way

Since, according to Master Yoda, “The greatest teacher, failure is,” you’ll first implement the tilt shift the naive way most first-timers would attempt.

let name = "\(indexPath.row).png"
let inputImage = UIImage(named: name)!
print("Tilt shifting image \(name)")

guard let filter = TiltShiftFilter(image: inputImage, radius: 3),
      let output = filter.outputImage else {
  print("Failed to generate tilt shift image")
  cell.display(image: nil)
  return cell
}
print("Generating UIImage for \(name)")
let fromRect = CGRect(origin: .zero, size: inputImage.size)
guard let cgImage = context.createCGImage(output, from: fromRect) else {
  print("No image generated")
  cell.display(image: nil)
  return cell
}

cell.display(image: UIImage(cgImage: cgImage))

print("Displaying \(name)")

Tilt shift almost correctly

It should come as no surprise that the Core Image operations should be placed into an Operation subclass at this point. You’re going to need both an input and an output image, so you’ll create those two properties. The input image should never change so it makes sense to pass it to the initializer and make it private.

import UIKit

final class TiltShiftOperation: Operation {
  var outputImage: UIImage?

  private let inputImage: UIImage

  init(image: UIImage) {
    inputImage = image
    super.init()
  }
}
private static let context = CIContext()
override func main() {
  guard let filter = TiltShiftFilter(image: inputImage, radius: 3),
        let output = filter.outputImage else {
    print("Failed to generate tilt shift image")
    return
  }

  let fromRect = CGRect(origin: .zero, size: inputImage.size)
  guard let cgImage = TiltShiftOperation
                          .context
                          .createCGImage(output, from: fromRect) else {
    print("No image generated")
    return
  }

  outputImage = UIImage(cgImage: cgImage)
}
override func tableView(
  _ tableView: UITableView, 
  cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  
  let cell = tableView.dequeueReusableCell(withIdentifier: "normal",
                                           for: indexPath) as! PhotoCell

  let image = UIImage(named: "\(indexPath.row).png")!

  print("Filtering")
  let op = TiltShiftOperation(image: image)
  op.start()

  cell.display(image: op.outputImage)
  print("Done")
  
  return cell
}

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.