Swift 一个小型游戏对象模型渐进式设计(五)——Swift 并发世界:把 Attackable 搬进 actor

为什么"并发"突然成了刚需

真实场景里:

  • 游戏服务器:32 条网络线程并发处理玩家技能;
  • 客户端:主线程发动画,后台线程算伤害,Timer 触发 dot;
  • 单机多核:SceneKit 物理回调、Vision 识别、Swift Concurrency Task 同时读写同一 BOSS 的血量。

如果还用传统锁:

swift 复制代码
objc_sync_enter(self)
hp -= damage
objc_sync_exit(self)

轻则性能抖动,重则死锁;而 Swift 5.5 起的 Actor 模型 把"互斥"升级为消息队列,编译期即可检查"跨 actor 引用是否安全",让"数据竞争"成为编译错误。

Actor 101:30 秒速览

  1. 定义
swift 复制代码
actor Boss {
    var hp: Double = 100
    func takeDamage(_ amount: Double) {
        hp = max(0, hp - amount)
    }
}
  1. 调用规则
  • 内部:同步函数,直接访问 hp
  • 外部:必须通过 await 异步消息,编译器自动加队列。
swift 复制代码
let boss = Boss()
await boss.takeDamage(10)   // 编译通过
boss.hp                     // ❌ 编译错误:actor-isolated
  1. 关键保证

Actor 隔离域(isolation domain):同一时间只有一条消息在执行,天然"可线性化"(Serializability)。

把协议能力搬进 actor

目标:

  • 不破坏前两篇的泛型协议架构;
  • 让任何实体既能以"值语义"跑在单线程,也能以" actor 引用"跑在多线程;
  • 客户端/服务器共用同一套算法。
  1. 定义并发版协议
swift 复制代码
/// 可并发受伤
protocol ConcurrentWoundable: AnyObject {
    associatedtype Value: NumericValue
    func takeDamage(_ amount: Value) async
    var currentHp: Value { get }
}

注意:

  • AnyObject 限制只让 class/actor 符合,因为需要共享引用;
  • 方法标记 async,调用方必须 await
  1. 让 actor 直接符合
swift 复制代码
actor ConcurrentBoss<Value: NumericValue>: ConcurrentWoundable {
    private(set) var hp: Value
    let maxHp: Value
    
    init(hp: Value, maxHp: Value) {
        self.hp = hp; self.maxHp = maxHp
    }
    
    func takeDamage(_ amount: Value) async {
        hp = max(Value(0), hp - amount)
    }
    
    nonisolated var currentHp: Value { hp }   // 只读快照,无需 await
}

nonisolated 关键字:编译器允许外部同步读取,但不能写。

  1. 并发安全暴击算法

把上篇的 DamageCalculator 泛型算法保持值语义,计算过程无锁;只有最后 takeDamage 进 actor 才排队。

swift 复制代码
let calc = AnyDamageCalculator(Double.self) { base in base * 1.5 }
let damage = calc.calculate(base: 50)          // 无锁计算
await boss.takeDamage(damage)                  // 一次消息

分离"计算"与"状态变更":计算无锁、变更串行,兼顾性能与安全。

分布式 Actor:跨进程也能 "await boss.takeDamage"

Swift 5.9 起引入 distributed actor,同一语法即可跨进程/跨机器:

swift 复制代码
distributed actor RemoteBoss: ConcurrentWoundable {
    distributed func takeDamage(_ amount: Value) async {
        hp = max(Value(0), hp - amount)
    }
}

调用方:

swift 复制代码
let boss = try await RemoteBoss.resolve(id: bossID, using: .init())
await boss.takeDamage(30)

底层由 Swift gRPC 传输消息,开发者零成本获得分布式对象模型。

实战:并发 Boss 战模拟器

场景:

  • 4 个玩家并发放技能,伤害随机;
  • 1 个后台线程每 0.5 s 触发 dot;
  • 1 个渲染线程每帧读血量更新 UI;

代码:

swift 复制代码
protocol NumericValue: Comparable & Sendable {
    static func + (lhs: Self, rhs: Self) -> Self
    static func - (lhs: Self, rhs: Self) -> Self
    static func * (lhs: Self, rhs: Self) -> Self
    static func / (lhs: Self, rhs: Self) -> Self
    static func > (lhs: Self, rhs: Self) -> Bool   // 与标量乘
    init(_ value: Int)                               // 能从整数字面量初始化
}
extension Double: NumericValue {}

/// 可并发受伤
protocol ConcurrentWoundable: AnyObject {
    associatedtype Value: NumericValue
    func takeDamage(_ amount: Value) async
    var currentHp: Value { get }
}

// 1. 并发 BOSS
actor BossBattle: @preconcurrency ConcurrentWoundable {
    private(set) var hp: Double
    let maxHp: Double
    init(hp: Double) {
        self.hp = hp;
        self.maxHp = hp
    }
    
    func takeDamage(_ amount: Double) async {
        hp = max(0, hp - amount)
        if hp == 0 { print("BOSS 被击败!") }
    }
    
    var currentHp: Double { hp }
}

// 2. 玩家技能
func playerTask(id: Int, boss: BossBattle) async {
    for _ in 0..<5 {
        let damage = Double.random(in: 5...15)
        await boss.takeDamage(damage)
        print("Player\(id) 造成 \(damage)")
        try? await Task.sleep(for: .milliseconds(.random(in: 100...300)))
    }
}

// 3. dot 后台
func dotTask(boss: BossBattle) async {
    while await boss.currentHp > 0 {
        await boss.takeDamage(3)
        print("dot 3 点")
        try? await Task.sleep(for: .milliseconds(500))
    }
}

// 4. 渲染线程(只读)
func renderTask(boss: BossBattle) async {
    while await boss.currentHp > 0 {
        let hp = await boss.currentHp
        print("UI 血量:\(Int(hp))")
        try? await Task.sleep(for: .seconds(1/60))
    }
}

// 5. 启动

Task {
    let boss = BossBattle(hp: 100)
    let _ = await withDiscardingTaskGroup { group in
        for i in 1...4 {
            group.addTask {
                await playerTask(id: i, boss: boss)
            }
        }
        
        group.addTask {
            await dotTask(boss: boss)
        }
        
        group.addTask {
            await renderTask(boss: boss)
        }
    }
}

运行结果(节选):

erlang 复制代码
Player3 造成 11.0
Player1 造成 8.0
dot 3 点
UI 血量:78
...
BOSS 被击败!

全程无需手动加锁,编译器保证任何时刻只有一条消息在修改 hp

与 SwiftUI 无缝衔接

swift 复制代码
@MainActor
final class BossModel: ObservableObject {
    private let boss = BossBattle(hp: 100)
    
    @Published private(set) var hpText = ""
    
    func start() async {
        await renderLoop()
    }
    
    @MainActor
    private func renderLoop() async {
        while await boss.currentHp > 0 {
            hpText = "血量 \(Int(await boss.currentHp))"
            try? await Task.sleep(for: .seconds(1))
        }
        hpText = "BOSS 被击败"
    }
    
    func attack() async {
        await boss.takeDamage(Double.random(in: 10...20))
    }
}

@MainActor 保证所有 SwiftUI 状态更新跑在主线程;业务逻辑在后台 actor 串行执行,零数据竞争。

常见坑与最佳实践

  1. 在 actor 里访问全局可变状态

    同样要 await,否则编译报错。

  2. nonisolated 只能读,不能写;写必须走消息。

  3. 不要把长时间阻塞代码(sleep、sync I/O)直接放进 actor,会卡住消息队列;应拆到 Task.detachedAsyncSequence

  4. 跨 actor 调用时,值类型会被拷贝,不要传递大型数组;可改用 AsyncSequence 流式输出。

  5. 分布式 actor 的方法参数/返回值必须遵循 Codable,否则无法序列化

相关推荐
报错小能手7 小时前
ios开发方向——swift错误处理:do/try/catch、Result、throws
开发语言·学习·ios·swift
小夏子_riotous9 小时前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
mCell13 小时前
MacOS 下实现 AI 操控电脑(Computer Use)的思考
macos·agent·swift
用户794572239541314 小时前
【DGCharts】iOS 图表渲染事实标准——8 种图表类型、高度可定制,3 行代码画出一条折线
swiftui·swift
chaoguo12341 天前
Any metadata 的内存布局
swift·metadata·value witness table
tangweiguo030519872 天前
SwiftUI布局完全指南:从入门到精通
ios·swift
用户79457223954133 天前
【RxSwift】Swift 版 ReactiveX,响应式编程优雅处理异步事件流
swift·rxswift
战族狼魂3 天前
XCode 发起视频 和 收到视频通话邀请实现双语功能 中文和俄语
swift
UXbot3 天前
2026年AI全链路产品开发工具对比:5款从创意到上线一站式平台深度解析
前端·ui·kotlin·软件构建·swift·原型模式
报错小能手3 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift