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

Swift Task内修改可变Struct是否安全?是否需使用Actor?

关于多线程修改共享状态导致Diffable Data Source崩溃的问题分析

核心问题:共享可变状态的线程不安全

你代码里的items和uniqueKeys是MyModelRepository这个类的属性,虽然它们是值类型(Array、Set),但因为被类持有,多个线程同时修改的本质是共享可变状态——主线程的removeAll()和Task后台线程的items.removeAll { ... }/uniqueKeys.remove(...)在并发执行,这会直接引发问题:

  • 比如主线程清空数组的同时,后台线程在遍历删除元素,数组内部结构被并发修改,直接触发崩溃;
  • 即使没崩溃,uniqueKeys和items的状态也会不一致,传给Diffable Data Source的快照会出现数据不匹配,导致UI错乱或后续崩溃。

值类型默认Sendable不代表多线程修改安全,Sendable只是保证类型可以安全跨线程传递,但修改同一个类持有的值类型实例依然会有线程安全问题——因为Array/Set的remove等mutating方法是原地修改自身,并发操作会破坏内部结构。

要不要用Actor?必须用(或等价的线程同步方案)

Actor是Swift解决共享可变状态线程安全的最优方案,它会自动确保所有内部状态的访问都是串行的,不会出现并发冲突。你可以直接把MyModelRepository改成Actor:

actor MyModelRepository {
    private var items: [MyModel] = []
    private(set) var uniqueKeys = Set<String>()

    func removeAll() {
        items.removeAll()
        uniqueKeys.removeAll()
    }

    func eventHandler() {
        do {
            let updates = try await retrieveUpdates()
            // 发送Publisher要切回主线程,因为UI侧订阅在主线程
            DispatchQueue.main.async {
                self.updateItemsPublisher.send(updates.updatedValues)
            }
            for item in updates.removed {
                items.removeAll { $0 == item }
                uniqueKeys.remove(item.key.orEmpty)
                guard let deletedModel = // 本地查找逻辑
                else { continue }
                DispatchQueue.main.async {
                    self.deleteItemPusblisher.send(deletedModel)
                }
            }
        } catch {
            // 错误日志
        }
    }
}

这样所有对items和uniqueKeys的修改都会在Actor的串行执行器中完成,彻底避免并发冲突。

替代方案:强制所有修改在主线程执行

如果不想用Actor,也可以把所有状态修改逻辑都切到主线程,比如把Task里的修改代码放到DispatchQueue.main.async中:

func eventHandler() {
    Task {
        do {
            let updates = try await retrieveUpdates()
            DispatchQueue.main.async {
                self.updateItemsPublisher.send(updates.updatedValues)
                for item in updates.removed {
                    self.items.removeAll { $0 == item }
                    self.uniqueKeys.remove(item.key.orEmpty)
                    guard let deletedModel = // 本地查找逻辑
                    else { continue }
                    self.deleteItemPusblisher.send(deletedModel)
                }
            }
        } catch {
            // 错误日志
        }
    }
}

这种方案更简单,但Actor的优势是编译时就检查线程安全,不会漏掉某个修改逻辑,适合复杂的状态管理场景。

崩溃根源

Diffable Data Source依赖数据源的一致性——生成快照时会假设数据源是稳定的。如果后台线程在修改数据源的同时,主线程正在生成快照,就会拿到半修改的状态,导致Diffable Data Source内部断言失败或数据不匹配,最终引发崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 08:42:44