Swift developer Matt Massicotte, formerly at Crashlytics and Apple, has recently launched a GitHub repo collecting a number of useful recipes to make it easier to use Swift concurrency and solve recurrent problems, while being aware of the most common traps you can fall into.
The Swift concurrent programming story has been evolving at a sustained pace since its inception with async/await
and actors support in Swift 5.5 through the introduction of full data isolation and structured concurrency in Swift 5.10. Mastering all the new concurrency-oriented functionality can be in itself quite a task, but to make things even harder, Swift concurrency primitives need to coexist with previous techniques and libraries, such as Grand Central Dispatch (GCD).
In this context, Massicotte's project aims to document and share both solutions and hazards you can face when adopting Swift concurrency and migrating away from GCD.
Swift Concurrency can be really hard to use. I thought it could be handy to document and share solutions and hazards you might face along the way. I am absolutely not saying this is comprehensive, or that the solutions presented are great. I'm learning too. Contributions are very welcome, especially for problems!
The repository is structured in several sections, each devoted to one major topic, from the very basic task of creating an asynchronous context to how to deal with concurrency within protocols, isolation, SwiftUI, and so on.
For example, Massicotte explains, one tricky detail about concurrency in SwiftUI is that only the body
accessor of a SwiftUI View
is MainActor
-safe, which makes any other function or property declared in a View
non isolated and exposed to the risk of race conditions. The solution he suggests is explicitly declaring the @MainActor
attribute for more complex views:
@MainActor
struct MyView: View {
var body: some View {
Text("Body")
}
}
Similarly, getting concurrency right with protocol can be complex, as Massicotte shows describing the case where you want to make a MainActor
-isolated type conform to a protocol whose definition you do not control.
As you may know, an actor needs to ensure only async
methods can access its state from outside, in which case the compiler can ensure no race conditions ever occur. Thus, if an actor-isolated type, e.g. a type that can only be used inside the MainActor
, must conform to a protocol that includes non-async methods, you need to take some care. The simplest solution is resorting to "non-isolated" conformance, instructing the compiler using the nonisolated
keyword, or you could prefer delegation instead to circumvent the issue altogether.
As mentioned, there are many problems that Massicotte describes together with potential solutions to them, many more than can be possibly covered here. Even when the suggested solutions may not be entirely satisfactory, the repository will certainly contribute to your awareness of things that could go wrong when using Swift concurrency, which makes it a highly useful resource.