Modern Concurrency: Beyond the Basics

Oct 20 2022 · Swift 5.5, iOS 15, Xcode 13.4

Part 2: Concurrent Code

18. Using a GlobalActor

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: 17. Creating a GlobalActor Next episode: 19. Challenge: Using a 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've reached locked video content where the transcript will be shown as obfuscated text.

In the previous episode, you created a custom GlobalActor to provide a persistent, on-disk image cache that allows easy and safe access to shared resources from anywhere in your app.

Wiring up the persistence layer

Before you do anything with ImageDatabase, you need to set it up safely by calling its setUp method. You can do that anywhere in your code but, for this example, you’ll do it along with the rest of your app setup.

try await ImageDatabase.shared.setUp()
ImageDatabase.shared.image(file.url)
ImageDatabase.shared.image(file.url)
Download: http://localhost:8080/gallery/image?26
In memory cache.
In memory cache.
Download: http://localhost:8080/gallery/image?2
In memory cache.
Download: http://localhost:8080/gallery/image?9
Download: http://localhost:8080/gallery/image?22
...
In memory cache.
In memory cache.
In memory cache.
In disk cache.
In disk cache.
Download: http://localhost:8080/gallery/image?10
In disk cache.

Adding a cache hit counter

The bottom bar has placeholders for information that helps you debug your caching mechanism.

@MainActor private(set) var inMemoryAccess: AsyncStream<Int>?
private var inMemoryAcccessContinuation: AsyncStream<Int>.Continuation?
private var inMemoryAccessCounter = 0
private var inMemoryAccessCounter = 0🟩 {
  didSet { inMemoryAcccessContinuation?.yield(inMemoryAccessCounter) }
}🟥
func setUp() async {
  let accessStream = AsyncStream<Int> { continuation in
    inMemoryAcccessContinuation = continuation
  }
}
func setUp() async {
  let accessStream = AsyncStream<Int> { continuation in
    inMemoryAcccessContinuation = continuation
  }
  🟩
  await MainActor.run { inMemoryAccess = accessStream }
  🟥
}
inMemoryAccessCounter += 1
deinit {
  inMemoryAcccessContinuation?.finish()
}

Displaying the counter

Now, you need to setup the image loader. A safe place to call ImageLoader.setUp() is your database’s own setUp().

await imageLoader.setUp()
.task {
  guard let memoryAccessSequence = 
    ImageDatabase.shared.imageLoader.inMemoryAccess else {
    return
  }
}
.task {
  guard let memoryAccessSequence = 
    ImageDatabase.shared.imageLoader.inMemoryAccess else {
    return
  }
  🟩
  for await count in memoryAccessSequence {
    inMemoryAccessCount = count
  }
  🟥
}

Purging the in-memory cache

You’ll soon wire up the button that clears the memory cache.

func clearInMemoryAssets() async {
  await imageLoader.clear()
  print("Cleared in-memory cache.")
}
Task {
  await ImageDatabase.shared.clearInMemoryAssets()
}
Task {
  await ImageDatabase.shared.clearInMemoryAssets()
  🟩
  try await model.loadImages()
  🟥
}