What is @preconcurrency?
@preconcurrency
is a Swift attribute that was introduced in Swift 5.5 alongside the major concurrency overhaul that brought us async/await, actors, and structured concurrency. It was added specifically to solve the chicken-and-egg problem of adopting modern concurrency: how do you use Swift's new concurrent features when most of your existing code and dependencies weren't designed with these strict safety rules in mind?
The attribute serves as a compatibility bridge, allowing developers to gradually migrate to Swift's concurrency model without having to rewrite everything at once or deal with an overwhelming number of compiler warnings.
The Background: Why @preconcurrency Exists
When Swift 5.5 introduced structured concurrency, it came with strict rules about what types can safely cross concurrency boundaries (called Sendable
types). This created immediate friction – suddenly, using UIKit in async functions, working with Core Data, or integrating popular third-party libraries would generate numerous warnings about potential thread safety issues.
Rather than forcing developers to choose between adopting modern concurrency or using existing frameworks, Apple introduced @preconcurrency
as a pragmatic solution. It essentially tells the compiler: "This code predates Swift concurrency, so don't apply the strictest safety checks, but let me use it responsibly in concurrent contexts."
The Problem @preconcurrency Solves
Swift's concurrency system is strict about safety. It wants to prevent data races and ensure your concurrent code is rock-solid. But this creates a problem: lots of existing frameworks and libraries were written before these rules existed.
Without @preconcurrency
, you'd see warnings like this:
import UIKit class ViewController: UIViewController { func updateUI() async { // ⚠️ Warning: UIViewController is not Sendable self.view.backgroundColor = .blue } }
The warning appears because UIKit wasn't designed with Swift's concurrency rules in mind. But we know UIKit is safe to use on the main thread.
How @preconcurrency Fixes This
Add @preconcurrency
to your import, and the warnings disappear:
@preconcurrency import UIKit class ViewController: UIViewController { func updateUI() async { // No warnings - Swift trusts you to use UIKit safely await MainActor.run { self.view.backgroundColor = .blue } } }
Example 1: Working with UIKit
Here's a practical example of loading and displaying an image asynchronously:
@preconcurrency import UIKit @MainActor class ImageViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() Task { await loadAndDisplayImage() } } func loadAndDisplayImage() async { do { // Load image on background queue let image = try await downloadImage(from: imageURL) // Update UI on main thread (thanks to @MainActor) imageView.image = image } catch { imageView.image = UIImage(systemName: "exclamationmark.triangle") } } private func downloadImage(from url: URL) async throws -> UIImage { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw ImageError.invalidData } return image } }
Without @preconcurrency import UIKit
, you'd get warnings about using UIKit types in async contexts.
Important Things to Remember
It's Your Responsibility: @preconcurrency
doesn't make code thread-safe – it just stops the compiler from warning you. You still need to ensure your code is actually safe to use concurrently.
It's Temporary: Think of @preconcurrency
as a migration tool. As frameworks get updated to support Swift concurrency properly, you should remove these annotations.
When NOT to Use @preconcurrency
- New code:
- When modern alternatives exist:
- Uncertainty about safety
The Bottom Line
@preconcurrency
is like a diplomatic passport for legacy code – it lets older APIs work smoothly in your modern async/await world. Use it thoughtfully during your transition to Swift concurrency, but always plan for the day when you won't need it anymore.
Top comments (1)
The attribute serves as a compatibility bridge, allowing developers to gradually migrate to Swift's concurrency model without having to rewrite everything at once or deal with an overwhelming number of compiler warnings.