You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
  }
}

潜在问题排查

  1. 阻塞式休眠风险:原代码用usleep会阻塞当前线程,可能耗尽并发线程池资源,改用Task.sleep(非阻塞)更贴合Swift并发模型的设计。
  2. 冗余变量浪费:原代码中的private var count: Int完全多余,Actor已经持有唯一的可信计数源,删除后不影响功能。
  3. 线程安全验证:Actor的increment方法已保证计数操作原子性,不会出现数据竞争;@MainActor修饰ObservableCounter后,@Published属性的更新自动在主线程执行,避免SwiftUI线程安全警告。
  4. 计数差异说明:buttonClickCount是即时更新的本地状态,counterValue是延迟后更新的实际请求完成计数,两者的时间差是业务逻辑正常现象,若需严格对齐,可将按钮点击也纳入Actor的计数逻辑。

内容的提问来源于stack exchange,提问作者altimes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 16:57:36