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

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. In this episode, you'll put it to work in Emoji Art. Continue with your project from the previous episode or open the starter project. 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 do it along with the rest of your app setup. Open LoadingView.swift. Scroll to Task. The first thing you do in the app is call model.loadImages in the task modifier. Set up ImageDatabase just before this line. Now start replacing calls to ImageLoader with calls to ImageDatabase, which transparently uses ImageLoader when an image isn't in the disk cache. First, in ThumbImage.swift, delete the ImageLoader property. You won't be using it anymore. This shows you where it's used. So replace ImageLoader.image inside the task closure. This checks the in-memory cache, then the on-disk cache and then if all else fails, the network. The other ImageLoader call is in DetailsView.swift. Again, delete the ImageLoader property. Then replace ImageLoader with ImageDatabase.shared. Build and run. Watch the output console while you scroll. You'll see a mix of network requests and assets cached in memory. Once you've downloaded all the images, you'll only see memory hits. This is great. Your pair of actors work well together. Now try this. Stop the app. And run it again. Don't scroll the feed just yet. This time, the disk cache serves all the content that the previous run fetched. Sometimes you'll see a network request. These are assets that failed to download on the previous run of the app. The app retries fetching those because they're not persisted on disk. Scroll down to the bottom and up again. After loading all the assets from disk, the log again fills up with messages from memory cached assets. Congratulations, you've created a super powerful image caching mechanism for your project. You still need to complete a few more tasks. The bottom bar has placeholders for information that helps you debug your caching mechanism. These two buttons should clear the disk cache and the in-memory cache. This label should show how many assets you loaded from disk and how many from memory. You'll get to work implementing these now. ImageLoader needs to continuously publish the count of cache hits. The perfect job for an AsyncStream. In ImageLoader.swift, add an AsyncStream property. inMemoryAccess is an asynchronous stream that runs on the main actor. Your views can access and subscribe to this property without worrying about any background UI updates. Set up two more properties. To produce ongoing updates, inMemoryAccess will be a buffered AsyncStream, and you'll store its continuation in inMemoryAccessContinuation. ImageLoader's actor semantics protect the current count in inMemoryAccessCounter from data races. Next, add an accessor to the counter. This didSet accessor sends any updates to inMemoryAccessCounter to the continuation if one exists. Add a setUp method. You initialize the stream in setUp and store its continuation in inMemoryAccessContinuation. Save the stream in your property. You switch to the MainActor to store the stream in inMemoryAccess, which runs on the MainActor. Now you can produce new values any time by calling inMemoryAccessContinuation.yield. The image method should increment this counter in case.completed. Do this before return statement. When you increase the hit counter, its didSet accessor yields the result to the stored continuation. Since both properties are on the actor, you perform both operations synchronously. However, the MainActor's annotation causes the stream to produce the value on the MainActor asynchronously. You're a good developer, so add a deinitializer. You manually complete the stream when the actor is released from memory. Now you need to set up the ImageLoader. A safe place to call ImageLoader.setup is your database's own setup. In ImageDatabase.swift, find setup, and do this at the end. Next, you need to display this memory cache hit counter in the toolbar. Open BottomToolbar.swift. Add a new task modifier after the last padding in the code. First, unwrap the optional stream. Then asynchronously iterate over the sequence. Each time the stream produces a value, you assign it to inMemoryAccessCount, a state property on the toolbar view that you use to display the text in the toolbar. Build and run again. Scroll up and down a little. You'll see the in-memory counter gives you updates in real time. You'll soon wire up the button that clears the memory cache. But first, add a new method at the bottom of ImageDatabase to purge the in-memory assets. You just call the ImageLoader's method to clear its cache, then print a message. Go back to BottomToolbar.swift. Find the comment clear in-memory cache. This code is for the right button in the toolbar. Add a task here to call your new ImageDatabase method. So first, you clear the in-memory cache, and then reload the images. Build and run. Scroll a little to add assets to the in-memory cache. Then tap the button to clear the memory. The message appears, then the app reloads assets from the disk cache or from the network. The Emoji Art app is now almost complete. You've done a fantastic job working through all the steps so far. Next up, complete the challenge to connect the second counter in the debugging toolbar, which displays on-disk cache hits and also implement the corresponding clear button.