如何在Combine中最优创建@Published值的聚合发布者
解决方案
核心问题分析
- 取值错误的根源是
@Published的默认发布时机是属性的willSet阶段:属性值即将变更但还未完成赋值时就发出事件,此时访问实例属性拿到的还是旧值 - 原有实现嵌套多层
switchToLatest逻辑冗余,可通过单次扁平映射完成需求
最优实现代码
import Combine class Foo: ObservableObject { @Published var bar: Int = 0 } class Foobar: ObservableObject { @Published var foo: [Foo] = [] var sumPublisher: AnyPublisher<Int, Never> { $foo // 跳过willSet阶段,等属性赋值完成后再执行后续逻辑,保证拿到最新值 .receive(on: DispatchQueue.main) // 每次数组变更,切换到新数组内所有bar属性的变更监听 .flatMapLatest { latestFooArray -> AnyPublisher<Int, Never> in // 合并数组内所有Foo实例的bar变更发布者 return Publishers.MergeMany(latestFooArray.map { $0.$bar }) // 任何bar变更时,用最新数组计算总和 .map { _ in latestFooArray.reduce(0) { $0 + $1.bar } } // 数组刚变更时立刻输出当前总和,不需要等待bar变更 .prepend(latestFooArray.reduce(0) { $0 + $1.bar }) .eraseToAnyPublisher() } .removeDuplicates() .eraseToAnyPublisher() } } // 低版本Combine不支持flatMapLatest的话可以替换为如下写法 /* var sumPublisher: AnyPublisher<Int, Never> { $foo .receive(on: DispatchQueue.main) .map { latestFooArray in Publishers.MergeMany(latestFooArray.map { $0.$bar }) .map { _ in latestFooArray.reduce(0) { $0 + $1.bar } } .prepend(latestFooArray.reduce(0) { $0 + $1.bar }) } .switchToLatest() .removeDuplicates() .eraseToAnyPublisher() } */
方案说明
- 完全基于
@Published原生能力实现,输出时机和SwiftUI视图更新完全同步,满足使用要求 - 仅用单层映射逻辑,比原有实现精简70%以上
- 自动处理数组增删元素、数组内元素属性变更两种场景,所有场景下取值都是最新值
- 聚合逻辑可随意替换,只需要修改
reduce部分的代码即可实现任意复杂的聚合计算 - 无循环引用风险
内容的提问来源于stack exchange,提问作者Michael J
相关产品推荐
相关产品推荐

