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
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 14. Actor Next episode: 16. GlobalActor

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

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

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

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

Unlock now

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

00:10In 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] = [:]

01:03ImageLoader 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()
}

02:42Since 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)
}

06:11The 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)
}