Swift 中的 @preconcurrency
随着 Swift 并发模型的成熟,开发者在集成旧代码或第三方库时面临着一个挑战:如何在严格的并发检查下使用非并发安全的 API?@preconcurrency 属性包装器正是为解决这一问题而生。
什么是 @preconcurrency?
@preconcurrency 是从 Swift 5.6 开始引入的一个修饰符,它允许开发者以更宽松的方式导入尚未完全适配 Swift 严格并发检查的模块。它不是 SwiftUI 独有的,但在 SwiftUI 开发中特别有用,因为 SwiftUI 应用常常需要集成各种异步数据源和第三方库。
为什么需要 @preconcurrency?
1. 严格的并发检查
Swift 6 引入了更严格的并发检查
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
默认情况下会:
- 禁止在非主线程访问 UI 相关代码
- 强制标记可能跨隔离域访问的数据
- 检查
Sendable一致性
2. 现实世界的代码迁移
许多现有库和框架尚未完全适配这些严格的规则,但开发者仍需要在 Swift 6 项目中使用它们。
使用场景
场景一:导入旧版库
@preconcurrency import LegacyNetworkLibrary
class DataViewModel: ObservableObject {
@Published var data: [String] = []
func fetchData() async {
let result = await LegacyNetworkLibrary.fetchFromNetwork()
await MainActor.run {
self.data = result
}
}
}
场景二:针对单个类型
@preconcurrency
@MainActor
public final class SomeViewModel {
public private(set) var items: [Item] = []
public init() {}
public func loadData() {
//...
}
}
注意事项
1. 不是长期解决方案
@preconcurrency 应该被视为迁移工具,而非永久解决方案:
// 应该做的事:
// 1. 使用 @preconcurrency 让代码通过编译
// 2. 向库维护者报告并发问题
// 3. 计划迁移到并发安全版本
// 不应该做的事:
// 依赖 @preconcurrency 作为永久解决方案
2. 性能考虑
使用 @preconcurrency 可能意味着错过编译器的并发优化:
// 没有 @preconcurrency - 编译器可以进行更多优化
func optimizedFetch() async -> Data {
return await ModernSDK.fetch()
}
// 使用 @preconcurrency - 优化机会减少
@preconcurrency
func lessOptimizedFetch() async -> Data {
return await LegacySDK.fetch()
}
3. 线程安全责任
当使用 @preconcurrency 时,你需要自己确保线程安全:
@preconcurrency import NonThreadSafeSDK
class DataManager {
private let sdkInstance: NonThreadSafeSDK.Instance
private let queue = DispatchQueue(label: "com.example.datamanager")
// 需要手动处理线程安全
func updateData() async {
await withCheckedContinuation { continuation in
queue.async {
// 确保在正确的队列上访问
self.sdkInstance.update()
continuation.resume()
}
}
}
}
总结
@preconcurrency 是 Swift 并发迁移过程中的重要工具,它提供了以下功能:
- 兼容性:允许在严格并发检查下使用旧代码
- 渐进迁移:让团队可以逐步更新代码库
但请记住, @preconcurrency 只是通往这个目标的桥梁,而不是目的地。