Chapters

Hide chapters

Concurrency by Tutorials

Second Edition · iOS 13 · Swift 5.1 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

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.

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.
© 2024 Kodeco Inc.

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 Kodeco Personal Plan.

Unlock now