Add guidance on Concurrency (#47)
* Add guidance on Concurrency * Update the example to be more generalized * Fix grammer error
This commit is contained in:
Родитель
577eaf7760
Коммит
4faebb5f00
|
@ -0,0 +1,84 @@
|
|||
# Concurrency
|
||||
|
||||
In general, follow the concurrency section in the [official guidelines](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html).
|
||||
|
||||
## Convention
|
||||
|
||||
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](https://developer.apple.com/wwdc21/10133).
|
||||
|
||||
## Rationale
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
### Bad: when calling the function image(from:url:) across different threads, it's possible that multiple downloads are executed which is inefficient and unnecessary. If the download function always downloads to the same file path, the file path might contain partially downloaded contents.
|
||||
|
||||
``` swift
|
||||
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] = [:]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Good: Use a task that downloads the executable asynchronously. Subsequent requests of the same image will wait for the asynchronous task to finish if queued before the cache is populated or use the cached value after the task finishes.
|
||||
|
||||
``` swift
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
|
@ -10,6 +10,7 @@ The Swift coding conventions guide documents many best practices for writing Swi
|
|||
|
||||
* [Annotations and Modifiers](AnnotationsAndModifiers.md)
|
||||
* [Comments](Comments.md)
|
||||
* [Concurrency](Concurrency.md)
|
||||
* [Delegates](Delegates.md)
|
||||
* [Early Return](EarlyReturn.md)
|
||||
* [File Organization](FileOrganization.md)
|
||||
|
|
Загрузка…
Ссылка в новой задаче