The @preconcurrency attribute is part of the tools that help you incrementally migrate to strict concurrency checking. When async/await was introduced by Apple, we were writing non-structured asynchronous code, mainly using closures. On our road to Swift 6, we must prepare our projects for strict concurrency checks done by the compiler. The SE-0337 proposal makes it possible to migrate incrementally.
Before diving into the details about the attribute, I encourage you to read the following articles:
- Swift 6: Preparing your Xcode projects for the future
- Async await in Swift explained with code examples
- Sendable and @Sendable closures explained with code examples
Those articles introduce you to Swift Concurrency and help you better understand why we need to think about managing pre-concurrency code.
What is the @preconcurrency attribute?
Like @unchecked Sendable, the @preconcurrency attribute helps you silence concurrency-related warnings without implementing Sendable conformance. While updating your codebase to make use of the latest concurrency changes, you might have been running into the following warning:
Add ‘@preconcurrency’ to suppress ‘Sendable’-related warnings from module ‘<some module>’
The Xcode compiler recognizes 3rd-party modules that are not in your control. In other words, libraries that you can’t easily migrate to work with Swift Concurrency. In those cases, you can suppress warnings triggered from those libraries by using the @preconcurrency attribute before an import:
@preconcurrency import Auth0
The compiler will no longer trigger warnings related to code from this specific library. While adding the attribute and getting rid of many warnings is easy, it’s essential to understand the risks you take.
The risks of using @preconcurrency
In an ideal world, there should be no risks when adding the @preconcurrency attribute. If the library owners did a good job, all their code is thread-safe, and they prevented data races by synchronizing access.
However, it could also be that none of the code is thread-safe. Usually, we would be warned by the compiler due to missing Sendable conformance:
Capture of ‘highlight’ with non-sendable type ‘SWHighlight?’ in a
@Sendable
closure
These warnings will no longer be triggered since we marked the library as a pre-concurrency import. You are taking ownership of ensuring thread-safe code. Keep this in mind when adding the attribute and try to see if an alternative solution is available, like updating the dependency to a newer version that possibly supports concurrency.
Should I import all 3rd party libraries using @preconcurrency?
Importing all 3rd party libraries using the @preconcurrency attribute by default might be tempting. However, if there’s no need to import a library to prevent concurrency warnings, it’s better not to do it. The compiler will warn you if you’ve imported a library using the attribute for no reason:
‘@preconcurrency’ attribute on module ‘Alamofire’ is unused
By not using the attribute and keeping the warnings visible, you allow yourself to revisit the code in the future when a fix from the 3rd party maintainer might be available. Suppressing the warnings can feel like a great solution today, but it will also hide possibly vulnerable code in your project.
Revisiting pre-concurrency imports regularly
If you decide to use the @preconcurrency import, planning a revisit of your code is essential. Once the library maintainers add support for concurrency, you can remove the attribute and see whether new warnings appear in your code that you need to fix. Any Sendable
-related failures involving that module will no longer suggest the @preconcurrency import and will result in errors once using Swift 6.
Continuing your journey into Swift Concurrency
The concurrency changes are more than imports and include many new features you can benefit from in your code. Now that you’ve learned about pre-concurrency maintenance, it’s time to dive into other concurrency features:
- MainActor usage in Swift explained to dispatch to the main thread
- How to Use URLSession with Async/Await for Network Requests in Swift
- Async await in Swift explained with code examples
- Swift 6: Incrementally migrate your Xcode projects and packages
- Concurrency-safe global variables to prevent data races
- Unit testing async/await Swift code
- Thread dispatching and Actors: understanding execution
- @preconcurrency: Incremental migration to concurrency checking
- Detached Tasks in Swift explained with code examples
- Task Groups in Swift explained with code examples
- Sendable and @Sendable closures explained with code examples
- AsyncSequence explained with Code Examples
- AsyncThrowingStream and AsyncStream explained with code examples
- Tasks in Swift explained with code examples
- Nonisolated and isolated keywords: Understanding Actor isolation
- Async let explained: call async functions in parallel
- Actors in Swift: how to use and prevent data races
Conclusion
The @preconcurrency attribute works excellently to temporarily suppress warnings for libraries that are out of your control. It’s essential to be aware of the risks it brings since you’ll no longer get Sendable-related warnings. You should plan a revisit of your pre-concurrency imports to make sure you’re updating your code once a concurrency-supporting alternative becomes available.
If you like to learn more tips on Swift, check out the Swift category page. Feel free to contact me or tweet me on Twitter if you have any additional suggestions or feedback.
Thanks!