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.