SwiftUI异步计数器优化问询:基于Actor的原子计数器简化方案
简化方案与潜在问题排查
问题背景
开发一款从网络获取数据的个人应用,需要跟踪已返回数据的数量。首次实现时出现间歇性数据竞争,遂用Actor实现原子计数器,但当前代码因多层async/await包装(@Published计数器包裹本地类计数器,再包裹Actor计数器)显得冗余。示例支持用户随意点击按钮,通过随机延迟模拟网络请求,需确保计数器同步更新。
原代码
import SwiftUI struct ContentView: View { @StateObject private var pickerCounter = ObservableCounter() @State private var buttonClickCount = 0 var body: some View { VStack { Text("Clicks recorded so far: \(pickerCounter.counterValue)") Button("Start async action") { buttonClickCount += 1 Task { pickerCounter.increment() } } Text("Button clicks \(buttonClickCount)") } } } actor CounterActor { private(set) var count: Int = 0 /// Increment internal counter by the given value func increment(by increment: Int = 1) -> Int { count += increment return count } } class ObservableCounter: ObservableObject { @Published var counterValue: Int = 0 private var count: Int = 0 private var counter = CounterActor() func increment( by value: Int = 1) { Task { let delay = Int.random(in: 0...100) usleep(useconds_t(60_000*delay)) await setCount(counter.increment()) } } @MainActor func setCount(_ count:Int) { counterValue = count } }
简化方案
可以去掉冗余的本地变量、合并重复逻辑,同时替换阻塞式休眠为Swift并发模型推荐的非阻塞方式,简化后代码如下:
import SwiftUI struct ContentView: View { @StateObject private var pickerCounter = ObservableCounter() @State private var buttonClickCount = 0 var body: some View { VStack { Text("Clicks recorded so far: \(pickerCounter.counterValue)") Button("Start async action") { buttonClickCount += 1 Task { await pickerCounter.increment() } } Text("Button clicks \(buttonClickCount)") } } } actor CounterActor { private(set) var count: Int = 0 func increment(by increment: Int = 1) -> Int { count += increment return count } } // 用@MainActor修饰,确保UI更新自动在主线程执行 @MainActor class ObservableCounter: ObservableObject { @Published var counterValue: Int = 0 private let counter = CounterActor() func increment(by value: Int = 1) async { // 非阻塞式休眠,替换原有的usleep let delay = UInt64.random(in: 0...100) * 60_000_000 // 转换为纳秒 try? await Task.sleep(nanoseconds: delay) // 直接获取Actor更新后的计数并同步到UI counterValue = await counter.increment(by: value) } }
潜在问题排查
- 阻塞式休眠风险:原代码用
usleep会阻塞当前线程,可能耗尽并发线程池资源,改用Task.sleep(非阻塞)更贴合Swift并发模型的设计。 - 冗余变量浪费:原代码中的
private var count: Int完全多余,Actor已经持有唯一的可信计数源,删除后不影响功能。 - 线程安全验证:Actor的
increment方法已保证计数操作原子性,不会出现数据竞争;@MainActor修饰ObservableCounter后,@Published属性的更新自动在主线程执行,避免SwiftUI线程安全警告。 - 计数差异说明:
buttonClickCount是即时更新的本地状态,counterValue是延迟后更新的实际请求完成计数,两者的时间差是业务逻辑正常现象,若需严格对齐,可将按钮点击也纳入Actor的计数逻辑。
内容的提问来源于stack exchange,提问作者altimes
相关产品推荐
相关产品推荐

