Swift 并发:Actor、isolated、nonisolated 完全导读

Actor 是什么?

Actor 是引用类型(与 class 类似),但自带隔离域:

  • 任意时刻只允许一个任务进入 Actor 内部
  • 外部调用必须加 await,自动排队
  • 编译器保证无数据竞争,无需手动加锁

对比:手动锁 vs Actor

swift 复制代码
// ❌ 旧世界:DispatchQueue + 锁
class Counter {
    private var value = 0
    private let queue = DispatchQueue(label: "counter")
    
    func increment() {
        queue.sync { value += 1 }
    }
}

// ✅ 新世界:Actor
actor Counter {
    var value = 0
    
    func increment() {
        value += 1          // 无需锁,编译器保护
    }
}

→ 代码量减半,永远不会忘记加锁。

Actor 使用范式

swift 复制代码
let counter = Counter()

Task {
    await counter.increment()           // 必须 await
    let current = await counter.value   // 串行排队
    print("Value \(current)")
}

规则速记:"进 Actor 先 await,出来无需 await。"

nonisolated: actor 里的"快速通道"

当方法不触碰 Actor 的任何可变状态时,可标 nonisolated

  • 跳过排队 → 性能更高
  • 无需 await → 调用方更轻松
  • 但不能读/写任何属性(除非属性也是 nonisolated

示例

swift 复制代码
actor Logger {
    nonisolated func appName() -> String {
        "MyCoolApp"   // 只返回常量,无状态访问
    }
    
    func log(_ msg: String) {
        print("[\(Date())] \(msg)")  // 访问状态,需 await
    }
}

Task {
    print(Logger().appName())      // ✅ 同步调用
    await Logger().log("Hello")    // ✅ 需 await
}

isolated:把"外部函数"拉进 actor 内部

场景:在 extension 或工具方法里,想同步访问 actor 状态,又不想 await

做法:把 actor 自身作为参数标 isolated

swift 复制代码
actor StatsTracker {
    var total = 0
    var count = 0
    
    func showAverage() {
        // 身处 actor 内部,可直接调用
        print(average(from: self))
    }
}

extension StatsTracker {
    // 外部函数,但获得"内部通行证"
    func average(from tracker: isolated StatsTracker) -> Double {
        Double(tracker.total) / Double(tracker.count)   // ✅ 无 await
    }
}

func average1(from tracker: isolated StatsTracker) -> Double {
    Double(tracker.total) / Double(tracker.count)   // ✅ 无 await
}

Task {
    let st = StatsTracker()
    // 这里调用的时候需要加await
    let s = await average1(from: st)
}

调用限制:

  • 只能在同一 actor 隔离域里使用
  • 外部世界无法直接 await average() → 编译器挡住

实战:UserService 完整例子

swift 复制代码
actor UserService {
    private var cache: [Int: String] = [:]

    // Actor 隔离:可能挂起
    func fetchUser(id: Int) async -> String {
        if let name = cache[id] { return name }
        
        try? await Task.sleep(nanoseconds: 500_000_000) // 模拟网络
        let name = "User\(id)"
        cache[id] = name
        return name
    }
    
    // 非隔离:立即返回
    nonisolated func version() -> String {
        "UserService v1.0"
    }
}

使用侧:

swift 复制代码
let service = UserService()

Task {
    print(service.version())              // 同步,无 await
    print(await service.fetchUser(id: 42)) // 第一次,慢
    print(await service.fetchUser(id: 42)) // 第二次,缓存瞬间返回
}

三条口诀一张图

场景 关键词 是否 await 性能
进 actor 内部 await 正常排队
不碰状态的工具 nonisolated 快速通道
外部函数想进内部 isolated Actor ❌(内部用) 零开销

常见编译错误速查

错误 原因 修复
Actor-isolated property can only be referenced with 'await' 少写 await 加上 await
Call to nonisolated function must not be marked with 'await' 多了 await 去掉 await
'isolated' parameter can only be called from within the same actor 外部直接调 isolated方法 把逻辑移到 actor 内部或使用普通扩展

什么时候用 Actor: checklist

✅ 适合

  • 多任务同时读/写同一可变状态(计数器、缓存、状态机)
  • 需要编译期保证无数据竞争
  • 与 SwiftUI、CoreData 主线程栈搭配

❌ 不适合

  • 只读常量或纯函数 → 用 struct
  • 跨隔离频繁大量小调用 → 队列可能成为瓶颈
  • 已有无锁队列/原子方案且极致性能

一句话总结

"Actor = 自动锁 + 编译器检查;nonisolated 开快道,isolated 让外部函数进内部。"

把 Actor 当作"线程安全保险箱":

重要物品(可变状态)放进去,拿的时候await排队;

只读文件(nonisolated)走侧门,不排队;

临时工具(isolated)让内部员工用,零等待。

用好这三板斧,写并发代码就像写单线程一样轻松!

相关推荐
用户38034165882972 小时前
视频编辑器的滤镜图内核:Node/Pin 模型怎么设计,撤销重做怎么才不崩
ios
CocoaKier14 小时前
苹果续费成功,几天后付费协议突然失效,导致线上苹果支付全部失败
ios·apple
深念Y1 天前
PVE 安装黑苹果并直通 Quadro P400
macos·ios·黑苹果·驱动·苹果·英伟达·p400
weixin-a153003083161 天前
7.Appium-ios端自动化
ios·appium·自动化
Anhty2 天前
2026九月最新变声器测评:iOS安卓双端适配,低延迟运行更稳定
android·人工智能·功能测试·ios·智能手机
_瑞2 天前
APM_OOMDetector
ios
秋雨梧桐叶落莳2 天前
【iOS】从源码深入理解RunLoop机制
macos·ios·objective-c·cocoa·cocoapods·uikit
烂蜻蜓2 天前
Flask入门教程(二十七):Session Interface API——自定义Session存储后端
网络·ios·flask
用户38034165882972 天前
VideoToolbox 硬编解码的十个坑:为什么它几乎从不报错
ios
bcbnb2 天前
Flutter-Notebook代码混淆:Android与iOS平台安全配置
后端·ios