Understanding @preconcurrency in Swift
swift preconcurrency Estimated reading time: 2 minutesSwift’s @preconcurrency attribute is a powerful tool introduced to help developers integrate legacy APIs into Swift’s modern concurrency model. It ensures compatibility and suppresses compiler warnings when working with older APIs that are not explicitly marked as concurrency-safe.
What is @preconcurrency?
The @preconcurrency attribute is used to mark types, functions, or protocol conformances that were designed before Swift’s concurrency features (async/await, actors, etc.). It informs the compiler that these APIs are safe to use in concurrent contexts, even though they may not conform to Sendable or other concurrency requirements.
Why is @preconcurrency Needed?
Swift’s concurrency model enforces strict thread safety rules, such as requiring types used in concurrent contexts to conform to Sendable. When working with legacy APIs that predate these rules, you may encounter warnings or errors. The @preconcurrency attribute bridges this gap by:
- Suppressing concurrency-related warnings for legacy code.
- Enabling developers to integrate legacy APIs without sacrificing safety or functionality.
How to Use @preconcurrency
The @preconcurrency attribute can be applied to:
- Classes, structs, and enums.
- Protocol conformances.
- Functions or methods.
Example 1: Marking Types
Here’s how to mark a legacy class with @preconcurrency to suppress concurrency warnings:
@preconcurrency
class LegacyAPIWrapper {
func fetchData() {
print("Fetching data from legacy API...")
}
}
let wrapper = LegacyAPIWrapper()
Task {
await withCheckedContinuation { continuation in
wrapper.fetchData()
continuation.resume()
}
}
This ensures the LegacyAPIWrapper can be used safely in concurrent contexts.
Example 2: Marking Protocol Conformances
Suppose a protocol predates Swift’s concurrency model. You can use @preconcurrency to mark a conforming class as compatible.
protocol LegacyProtocol {
func performTask()
}
@preconcurrency
class ConformingClass: LegacyProtocol {
func performTask() {
print("Performing task in a legacy way...")
}
}
let conformingInstance = ConformingClass()
Task {
await withCheckedContinuation { continuation in
conformingInstance.performTask()
continuation.resume()
}
}
Example 3: Combining @MainActor with @preconcurrency
Using @MainActor ensures a method or class operates on the main thread. When combined with @preconcurrency, you can suppress warnings for legacy APIs like UIDevice.
Scenario: Updating UI Based on Device Orientation
import UIKit
@MainActor
@preconcurrency
class OrientationHandler {
func handleOrientationChange() {
let device = UIDevice.current
switch device.orientation {
case .portrait:
print("Device is in portrait mode")
case .landscapeLeft, .landscapeRight:
print("Device is in landscape mode")
default:
print("Device orientation is unknown")
}
}
}
let handler = OrientationHandler()
Task { @MainActor in
handler.handleOrientationChange()
}
Why @preconcurrency?
UIDevice predates Swift’s concurrency model and is not Sendable. Adding @preconcurrency ensures safe usage of UIDevice within an @MainActor context.
Conclusion
@preconcurrencyhelps integrate legacy APIs into Swift’s concurrency model.- It suppresses warnings for types or protocols not marked as
Sendable. - Combining
@MainActorwith@preconcurrencyis especially useful for UI-related legacy APIs.
Resources
Share on: