SwiftUI:含[any IItemViewModel]数组的ViewModel实现遇错误如何解决?
SwiftUI协议封装ObservableObject数组报错:Type 'any IItemViewModel' cannot conform to 'IItemViewModel'
问题代码及报错位置
尝试用协议封装包含ObservableObject的ViewModel数组,遍历数组传递元素给子View时触发错误,核心代码如下:
protocol IItemViewModel: ObservableObject { var title: String { get } } // 注意:无需重复声明ObservableObject,协议已继承 final class ItemViewModel: IItemViewModel { let title: String init(title: String) { self.title = title } } protocol IMainViewModel: ObservableObject { var list: [any IItemViewModel] { get } } final class MainViewModel: IMainViewModel { @Published var list = [any IItemViewModel]() required init() { list.append(ItemViewModel(title: "test1")) } } // 原代码约束错误:应改为ViewModel: IMainViewModel,而非MainViewModel struct MainView<ViewModel>: View where ViewModel: IMainViewModel { @ObservedObject var viewModel: ViewModel var body: some View { ScrollView(.vertical, showsIndicators: false) { LazyVStack(spacing: 0) { ForEach(viewModel.list, id: \.title) { model in ItemView(viewModel: model) // <-- 报错位置 } } } } } struct ItemView<ViewModel>: View where ViewModel: IItemViewModel { @ObservedObject var viewModel: ViewModel init(viewModel: ViewModel) { self.viewModel = viewModel } var body: some View { Text(viewModel.title) } }
报错原因
viewModel.list里的元素是存在类型any IItemViewModel,它是对所有遵守IItemViewModel协议类型的抽象,擦除了具体类型信息;而ItemView的泛型约束要求传入具体的、遵守协议的类型,存在类型无法直接满足这个泛型要求,因此触发错误。
两种可行解决方案
方案1:直接修改ItemView适配存在类型(最简单)
去掉ItemView的泛型约束,直接声明viewModel为any IItemViewModel类型,可直接接收数组中的元素:
struct ItemView: View { @ObservedObject var viewModel: any IItemViewModel var body: some View { Text(viewModel.title) } }
这种方案完全适配当前协议封装逻辑,代码改动最小。
方案2:类型擦除保留泛型设计(适合需要泛型灵活性的场景)
如果必须保留ItemView的泛型结构,可以创建类型擦除包装器,把any IItemViewModel转换为具体类型:
// 类型擦除的ObservableObject包装器 final class AnyItemViewModel: ObservableObject, IItemViewModel { let title: String private let objectWillChange: AnyPublisher<Void, Never> init<T: IItemViewModel>(_ viewModel: T) { self.title = viewModel.title self.objectWillChange = viewModel.objectWillChange.eraseToAnyPublisher() } } // 修改MainViewModel的list类型为具体擦除类型 final class MainViewModel: IMainViewModel { @Published var list = [AnyItemViewModel]() required init() { list.append(AnyItemViewModel(ItemViewModel(title: "test1"))) } } // 此时MainView的ForEach可正常传递元素给泛型ItemView struct MainView<ViewModel>: View where ViewModel: IMainViewModel { @ObservedObject var viewModel: ViewModel var body: some View { ScrollView(.vertical, showsIndicators: false) { LazyVStack(spacing: 0) { ForEach(viewModel.list, id: \.title) { model in ItemView(viewModel: model) } } } } }
额外修正点
原代码中MainView的泛型约束写错了,应改为ViewModel: IMainViewModel而非ViewModel: MainViewModel,否则协议封装的意义失效,只能传入MainViewModel这一种类型。
内容的提问来源于stack exchange,提问作者Kruiller
相关产品推荐
相关产品推荐

