Swift中为何无法将存在类型用作@ObservedObject包装的属性类型
protocol ExampleStore: ObservableObject { var text: String { get } } class ConcreteExampleStore: ExampleStore { @Published var text: String = "" } struct ExampleView: View { @ObservedObject private var store: any ExampleStore init(store: any ExampleStore) { self.store = store } var body: some View { Text(store.text) } }
编译时抛出错误:Type 'any ExampleStore' cannot conform to 'ObservableObject'(类型'any ExampleStore'无法遵循'ObservableObject'协议),报错位置为@ObservedObject修饰的store属性声明行。
Swift 5.7 确实开放了带关联类型协议作为存在类型(即any 协议名写法)的使用限制,但这里有个很容易踩的核心规则:存在类型本身永远不会遵循它所代表的协议。@ObservedObject对属性类型的硬要求是,被包裹的类型必须直接遵循ObservableObject协议。而any ExampleStore本质上是一个类型安全的"容器盒子",可以承载任意遵循ExampleStore协议的具体实例,但这个盒子自身并没有实现ObservableObject的协议要求——它既没法自动合成ObservableObject要求的objectWillChange发布者,也不会自动把内部装着的实例的变更事件向外转发,自然满足不了@ObservedObject的约束,编译报错完全符合预期。
这个限制不是Swift 5.7的特性缺失,是存在类型的设计逻辑决定的:存在类型的作用就是抹除具体类型信息,而协议遵循本身要求具体类型实现对应能力,二者设计目标本来就有差异。
- 泛型约束写法(优先使用)
这是SwiftUI场景下性能最好、兼容性最高的写法,编译期就能确定store的具体类型,没有额外运行时开销,SwiftUI可以正常监听对象的状态变更:
struct ExampleView<Store: ExampleStore>: View { @ObservedObject private var store: Store init(store: Store) { self.store = store } var body: some View { Text(store.text) } }
- 手动实现类型擦除包装
如果确实需要在运行时抹除store的具体类型,方便动态切换不同实现,可以自己写一个类型擦除的包装类,手动转发ObservableObject的事件:
import Combine class AnyExampleStore: ExampleStore { var text: String { getter() } private let getter: () -> String private var cancellable: AnyCancellable? init<S: ExampleStore>(_ wrapped: S) { getter = { wrapped.text } // 把内部实例的变更事件转发给当前包装实例 cancellable = wrapped.objectWillChange .sink(receiveValue: { [weak self] _ in self?.objectWillChange.send() }) } }
使用时把具体store实例包装成AnyExampleStore再传入View即可,这种方式会产生少量动态派发和订阅的开销,只适合确实需要动态切换store实现的场景。
内容的提问来源于stack exchange,提问作者hydro1337x

