Chapters

Hide chapters

Concurrency by Tutorials

Third Edition · iOS 16 · Swift 5.7 · Xcode 14

6. 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

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:

Asizixaas ofWaifh ojEyihaners ubRigmigmuv apRagohbom Omavafiaw Lxobar

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

BlockOperation 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 = "\(rowNumber).png"
let uiImage = UIImage(named: name)!
print("Tilt shifting image \(name)")

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

let result = UIImage(cgImage: cgImage)
image = Image(uiImage: result)

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 SwiftUI

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)
}
private func tiltShiftImage() {
  print("Filtering")
  let op = TiltShiftOperation(image: UIImage(named: "\(rowNumber).png")!)
  op.start()

  if let outputImage = op.outputImage {
    print("Updating image")
    image = Image(uiImage: outputImage)
  }

  print("Done")
}
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