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

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

相关推荐
wjm0410063 小时前
ios内存管理
ios·objective-c·swift·客户端开发
黑科技iOS上架3 小时前
ios应用被封号后再次上架很难么?
经验分享·ios
柚鸥ASO优化9 小时前
一篇讲透安卓ASO!开发者千万别只盯着iOS了
android·ios·aso优化
黑科技iOS上架9 小时前
Swift Package Manager包管理工具的优缺点
经验分享·ios
大熊猫侯佩13 小时前
Swift 6.4 的 Ref / MutableRef 大揭秘:给值类型开一扇“安全的小窗”
ios·swift·编程语言
黑科技iOS上架14 小时前
没有mac电脑如何借助windows系统上传ipa到App Store
经验分享·ios
Layer15 小时前
从 WWDC 26 空间重构(Spatial Reframing)再看端侧 2D 转 3D 的技术演进
ios·aigc
Cutecat_1 天前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
大熊猫侯佩1 天前
WWDC26 SwiftUI 进化之路:砸碎黑盒,彻底迎来开发自由!
ios·swiftui·swift
游戏开发爱好者81 天前
iPhone真机调试有哪些方法?一次定位推送权限问题时整理出来的几种方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程