Modern Concurrency: Beyond the Basics

Oct 20 2022 Swift 5.5, iOS 15, Xcode 13.4

Part 2: Concurrent Code

15. Writing Safe Concurrent Code With Actors

Episode complete

Play next episode

Next
Save for later
About this episode
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 14. Actor Next episode: 16. GlobalActor

This video Writing Safe Concurrent Code With Actors was last updated on Oct 20 2022

Heads up... You've reached locked video content where the transcript will be shown as obfuscated text.

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

In the previous episode, you converted EmojiArtModel from a class to an actor, to protect its verifiedCount property from data races.

A more complex actor

In this episode, you’ll mix actors, tasks and async/await to solve one of the eternal problems in programming: image caching.

enum DownloadState {
  case inProgress(Task<UIImage, Error>)
  case completed(UIImage)
  case failed
}
private(set) var cache: [String: DownloadState] = [:]

Filling the cache

ImageLoader has methods to add images to the cache, start a new download and clear the cache.

func add(_ image: UIImage, forKey key: String) {
  cache[key] = .completed(image)
}
let download: Task<UIImage, Error> = Task.detached {
  guard let url = URL(string: "http://localhost:8080".appending(serverPath))
  else {
    throw "Could not create the download URL"
  }
  print("Download: \(url.absoluteString)")
  let data = try await URLSession.shared.data(from: url).0
  return try resize(data, to: CGSize(width: 200, height: 200))
}

cache[serverPath] = .inProgress(download)
cache[serverPath] = .inProgress(download)
do {
  let result = try await download.value
  add(result, forKey: serverPath)
  return result
} catch {
  cache[serverPath] = .failed
  throw error
}
func clear() {
  cache.removeAll()
}

Sharing ImageLoader with views

Since you’ll use ImageLoader in a few different views, you need to inject it directly into the SwiftUI environment, so you can easily access it throughout your view hierarchy.

actor ImageLoader: ObservableObject
.environmentObject(ImageLoader())
@EnvironmentObject var imageLoader: ImageLoader
.task {
  guard let image = try? await imageLoader.image(file.url) else {
    overlay = "camera.metering.unknown"
    return
  }
  updateImage(image)
}

Using the cached assets

The server image feed returns some duplicate assets so you can play around with the scenario of getting an already-cached asset and displaying it.

Download: http://localhost:8080/gallery/image?11
Download: http://localhost:8080/gallery/image?16
Download: http://localhost:8080/gallery/image?23
Download: http://localhost:8080/gallery/image?26
@EnvironmentObject var imageLoader: ImageLoader
.task {
  image = try? await imageLoader.image(file.url)
}