手记

Swift 6的新并发特性解读:让代码更安全高效

从 @MainActor 隔离到线程安全检测,了解如何解决 Swift 6 严格的并发警告:让你的 iOS 应用程序为未来的并发挑战做好准备。

Ott Maidre 拍摄的照片:https://www.pexels.com/photo/medieval-armor-2046779/

Swift 6 中有哪些新的并发编程功能?

Swift 6 更加注重严格的并发检查以消除数据竞争和未定义行为。关键更新包括:

  • 更严格的**@MainActor**强制执行:编译器现在更积极地标记未显式标记为 @MainActor 的 UI 更新。
  • 完全的任务隔离:函数和属性必须显式声明 actor 隔离。
  • 全局 actor 的自定义:更好地控制全局 actor,如 @MainActor 或自定义的全局 actor。
  • 改进的诊断:线程不安全代码的警告更明确。
2. 真实的迁移挑战(从Xcode 15 迁移到 16 的实际挑战)

当你升级到 Xcode 16 时,你可能会遇到一些新的警告和错误。下面是一些处理常见情况的方法。

示例 1:@MainActor 提示

情景:一个视图模型更新UI元素但没有使用@MainActor,因此导致出现如下警告:
‘“Main actor-isolated property 'title' cannot be mutated from a non-isolated context”’。

之前

    class ProfileViewModel {  
        var title: String = "" // 警告: 这里没有使用actor隔离  

        func loadData() async {  
            let data = await fetchData()  
            title = data.title // 错误: 在非隔离环境下尝试修改数据  
        }  
    }

修好后

    @MainActor // 明确将整个类标记为主actor隔离的
    class ProfileViewModel: ObservableObject {
        @Published var title: String = ""

        func loadData() async {
            let data = await fetchData()
            title = data.title // ✅ 在主线程上执行。
        }
    }
示例 2:在线程间切换使用 DispatchQueue

场景:老代码里用 DispatchQueue.main.async 来更新 UI 元素。Swift 6 认为这种做法不安全,除非将其包裹在 MainActor 里。

未修复

    func updateUI() {  
        DispatchQueue.main.async { // ❌ 提示:推荐使用 MainActor.run 而不是这里的代码  
            self.label.text = "Hello"  
        }  
    }

搞定后

func 更新用户界面() async {  
    await MainActor.run { // ✅ 线程安全注释  
        self.label.text = "你好"  
    }  
}

示例 3:未隔离的全局状态

场景:多个线程访问同一个共享的 UserDefaults 管理器,共享的可变状态在没有并发保护的情况下被访问,引发了如下警告:
"Shared mutable state accessed without concurrency protection". (共享的可变状态在没有并发保护的情况下被访问)

未修复前

    class SettingsManager {  
        static let shared = SettingsManager()  
        var theme: String = "light" // ❌ 注意:此设置可能不是线程安全的  
    }

修好后

    @globalActor  
    struct SettingsActor {  
        actor ActorType { }  
        static let shared = ActorType()  
    }  

    @SettingsActor // 自定义全局Actor用于保证线程安全  
    class SettingsManager {  
        static let shared = SettingsManager()  
        var theme: String = "light" // 受SettingsActor保护  
    }
3. 常见警告及解决方法

警告:在 @MainActor 中使用了非发送类型

"不可发送的类型 'ViewController' 传递给隔离在主演员中的方法的隐式的异步调用"

解决一下问题

    class ViewController: UIViewController {  
        // 当按钮被点击时触发的函数
        func onButtonTap() {  
            // 使用 Task { @MainActor in } 将任务隔离在主线程中,以确保 UI 更新的线程安全
            Task { @MainActor in // ✅ Isolate the Task  
                self.updateUI()  
            }  
        }  

        // 使用 @MainActor 关键字标记的函数会在主线程中执行,确保 UI 更新不会导致线程冲突
        @MainActor  
        func updateUI() { // 更新 UI 的具体实现代码
            // 更新 UI 的具体实现代码
        }  
    }
警告 2: 在 @Sendable 闭包中使用 self

@Sendable 闭包中,使用非可发送类型 'MyClass' 来引用 'self'。

请解决

    Task { [weak self] in //✅ 使用弱引用来避免 retain 循环  
        guard let self else { return }  
        await self 的 fetchData()  
    }
警告 3: 在并发上下文中使用不符合 Sendable 要求的类型

"该函数不能标记为'@Sendable',因为其包含的' DataModel '类型参数不是可发送的。"

修复一下

    struct DataModel: Sendable { // ✅ 数据模型是线程安全的  
        let id: UUID  
        let value: String  
    }
  1. 如何在 Xcode 16 中调试并发代码

Swift 6 带来了一些强大的工具来检测并发错误。

1. 线程检查器 (TSan) 改进

TSan 现在可以检测 Swift 并发程序中的数据争用。可以通过以下方式启用它:
产品 > 方案 > 编辑方案 > 诊断 > 线程检测器

2. 任务追踪

可视化任务全过程,可以使用以下方法:

// 使用带有任务跟踪的await获取用户数据
await withTaskTracing {  
    await fetchUserData()  
}
3. -strict-concurrency 编译器选项

构建设置中开启严格的并发控制:

SWIFT_STRICT_CONCURRENCY = 启用
5. 迁移旧代码的最佳实践
  1. 逐步采用(@preconcurrency 导入):对于尚未更新到 Swift 6 的第三方库,使用 @preconcurrency import
  2. 使用编译器警告进行审计:将警告视为错误 (SWIFT_TREAT_WARNINGS_AS_ERRORS = YES)。
  3. 用 Actors 替换**DispatchQueue****:将基于 GCD 的代码替换为 Swift 原生的 actor@MainActor
  4. 逐步使用**Sendable****:将线程安全的类型标记为 Sendable 以启用编译器优化。
结论部分

Swift 6的并发模型是一个游戏规则的改变者,对于编写安全且高性能的应用程序来说是一个变革,但将现有代码库迁移到新的并发模型需要仔细处理@MainActor、线程隔离性以及Sendable类型。通过逐步解决常见的警告,并利用Xcode 16的调试工具,您可以使代码保持前瞻性,同时保持其稳定性。

要点

  • 尽量激进地使用 @MainActor 来更新 UI。
  • 用 Swift 的结构化并发特性替换掉 DispatchQueue
  • 尽早启用严格的并发检查功能。
  • 审核第三方依赖项以确保它们符合 Sendable 要求。
更多资料
0人推荐
随时随地看视频
慕课网APP