TCA中State结构体用引用类型属性无法触发UI更新的原因探究
TCA中State内引用类型属性修改无法触发UI更新
以下是简化的TCA应用State代码(其余代码可正常编译):
// 问题:当Dog为Class而非Struct时,在Reducer中修改dog的name属性无法触发UI更新 class Dog: Equatable { var name = "Spot" // Equatable协议实现 static func == (lhs: Dog, rhs: Dog) -> Bool { lhs.name == rhs.name } } struct DogManager: ReducerProtocol { struct State { var dog = Dog() } } // 省略Action枚举与reduce方法
问题核心
在Reducer的reduce方法中修改dog属性的状态时,无法触发UI更新:
// 遵循ReducerProtocol的结构体的reduce方法中 case .changeDogName: state.dog.name = "Fido" // 修改dog.name属性(假设dog是引用类型对象) // 不会触发UI更新
即使给Dog类添加ObservableObject协议,并给name属性添加@Published属性包装器,在reduce方法中修改dog.name仍无法触发UI更新。
已知两种解决方案:
- 将Dog改为Struct,此时一切功能正常。
- 在遵循
ReducerProtocol的结构体中,将Dog作为独立的ObservedObject属性添加,UI更新可正常工作。
疑问:为何将引用类型作为State结构体的属性并在Reducer中修改时,无法触发UI更新?原本认为Class遵循Equatable协议就足以触发UI更新。
原因分析
TCA的UI更新触发逻辑依赖State结构体的整体值变化检测,核心原因如下:
- TCA内部通过对比State的新旧实例(基于Equatable)判断是否触发UI刷新。当State是纯值类型结构体时,修改内部属性会生成全新的State实例,Equatable对比返回
false,从而触发更新。 - 当State包含引用类型(如Dog类)时,修改引用类型的内部属性,State结构体本身的存储内容并未改变——因为State中存的只是对象的引用指针。即便Dog实现了Equatable,TCA对比的是State结构体的整体值,也就是两个Dog对象的引用是否相等,而非调用Dog的
==方法比较内容。这就导致新旧State对比时被判定为“值未变化”,因此不会触发UI更新。 - 给Dog添加
ObservableObject和@Published无效,因为TCA的UI绑定是基于对State的订阅,而非直接监听ObservableObject的变化。State本身感知不到内部引用对象的内容变更,自然不会通知UI刷新。
可行方案
除了你提到的两种方案,还有一种兼容引用类型的临时写法:
- 修改引用类型内部属性后,手动创建新的对象实例替换State中的原有引用:
case .changeDogName: let newDog = Dog() newDog.name = "Fido" state.dog = newDog
这种方式会让State的dog引用发生变化,触发TCA的Equatable对比,从而触发UI更新。不过这种写法违背了TCA推荐的“值类型优先”设计原则,仅适合特殊场景下临时使用。
内容的提问来源于stack exchange,提问作者Small Talk
相关产品推荐
相关产品推荐

