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. While many of the frameworks have started adopting Swift Concurrency, you might still encounter a few that aren’t ready.
In these cases, the @preconcurrency attribute might be a great solution. How it works is what we’ll learn in this lesson.
What is the @preconcurrency attribute?
Like @unchecked Sendable, the @preconcurrency attribute helps you mute concurrency-related warnings without the need to implement Sendable conformance. While updating your codebase to incorporate the latest concurrency changes, you may have encountered the following warning:
Add ‘@preconcurrency’ to suppress ‘Sendable’-related warnings from module ‘<some module>’
The Xcode compiler detects third-party modules that are beyond your control. In other words, libraries that you can’t easily update to work with Swift Concurrency. In such cases, you can suppress warnings from those libraries by using the @preconcurrency attribute before an import.
@preconcurrency import <some_module>
The compiler will no longer generate warnings related to code from this specific library. While adding the attribute and eliminating many warnings is simple, it’s important to understand the risks involved.
The risks of using @preconcurrency
In an ideal world, there would be no risks when adding the @preconcurrency attribute. If the library owners did a good job, all their code is thread-safe, and they prevent data races by synchronizing access.
However, it could also be that none of the code is thread-safe. Usually, we are warned by the compiler for missing Sendable conformance:
Capture of ‘highlight’ with non-sendable type ‘SWHighlight?’ in a
@Sendableclosure
These warnings will no longer appear because we’ve marked the library as a pre-concurrency import. You are responsible for ensuring that the code is thread-safe. Keep this in mind when adding the attribute and consider whether an alternative, like updating the dependency to a newer version that supports concurrency, might be available.
Should I import all 3rd party libraries using @preconcurrency?
Importing all 3rd party libraries using the @preconcurrency attribute by default may 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 ‘<some_module>’ 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 may seem like a great solution today, but it will also hide potentially 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.
Summary
While @preconcurrency can be a great escape route for modules out of your control, it’s not always the best solution. Much better would be to update the dependency to a newer version if possible. However, if the module is fully out of your control, it might be useful to surpress the warnings and continue after using @preconcurrency.
In the next lesson, we’re going to look into migrating away from frameworks like Combine and RxSwift.