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)让内部员工用,零等待。

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

相关推荐
初级代码游戏10 小时前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏12 小时前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui15 小时前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift
zhangfeng113318 小时前
CSDN星图 支持大模型微调 trl axolotl Unsloth 趋动云 LLaMA-Factory Unsloth ms-swift 模型训练
服务器·人工智能·swift
Boxsc_midnight18 小时前
【openclaw+imessage】【免费无限流量】集成方案,支持iphone手机+macos
ios·智能手机·iphone
感谢地心引力1 天前
安卓、苹果手机无线投屏到Windows
android·windows·ios·智能手机·安卓·苹果·投屏
2501_915918412 天前
HTTPS 代理失效,启用双向认证(mTLS)的 iOS 应用网络怎么抓包调试
android·网络·ios·小程序·https·uni-app·iphone
Swift社区2 天前
Flutter 路由系统,对比 RN / Web / iOS 有什么本质不同?
前端·flutter·ios
zhyongrui2 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
Andy Dennis2 天前
ios开发 xcode配置
ios·cocoa·xcode