In general, follow the concurrency section in the official guidelines.
Use a task to manage synchronous access to a property populated by an asynchronous function within an Actor. For more in-depth discussions of the provided example, watch WWDC21 - Protect mutable state with Swift actors.
Even though Actors are helpful for sharing information between concurrent code, asynchronous functions in Actors do not guarantee the state of Actor properties after an asynchronous call.
actor ImageDownloader {
func image(from url: URL) async throws -> UIImage {
if let cached = cache[url] {
return cached
}
// When the following line executes, the function does not continue executing until
// the download finishes. During this time, a different thread could call the function
// and download the same image again.
let imageFilePath = try await downloadImage(from: url)
// The image file might not contain the full downloaded contents.
let image = UIImage(contentsOfFile: imageFilePath)
cache[url] = image
return image!
}
private var cache: [URL: UIImage] = [:]
}
actor ImageDownloader {
/// Checks the cache for the image corresponding to the url
/// - Returns: the cached image if available, or wait for the task to finish and return the result of the task
func image(from url: URL) async throws -> UIImage {
if let cached = cache[url] {
switch cached {
case .ready(let image):
return image
case .inProgress(let task):
return try await task.value
}
}
let task = Task {
let imageFilePath = try await downloadImage(from: url)
return UIImage(contentsOfFile: imageFilePath)!
}
cache[url] = .inProgress(task)
do {
let image = try await task.value
cache[url] = .ready(image)
return image
} catch {
cache[url] = nil
throw error
}
}
private var cache: [URL: CacheEntry] = [:]
private enum CacheEntry {
case inProgress(Task<UIImage, Error>)
case ready(UIImage)
}
}