Swift MVVM架构下编译器报'has no member'错误的解决方案咨询
MVVM架构中ViewModel抽象View接口的编译错误解决
我正在编写MVVM架构的样板代码,尝试在ViewModel中抽象View接口而非绑定属性,但编译器报错:Type 'V.Command' has no member 'reload'。目前临时解决办法是强制将命令转换为V.Command,但不够优雅,寻求更优解决方案。
相关代码
接口定义
protocol Actionable { associatedtype Command func perform(command: Command) } extension Actionable { func perform(command: Command) {} } protocol Rendering { associatedtype Model func render(with model: Model) } extension Rendering { func render(with dependencies: Model) {} } protocol VMView: Rendering, Actionable {} protocol ViewModel { associatedtype Command associatedtype Dependencies associatedtype Model }
View实现
final class NewViewVC: UIViewController, VMView { typealias Model = NewViewVM<NewViewVC>.Model typealias Command = NewViewVM<NewViewVC>.Command func perform(command: Command) { print("performing \(command)") } func render(with model: Model) { print("rendering \(model)") } var viewModel: NewViewVM<NewViewVC>! override func viewDidLoad() { super.viewDidLoad() } } extension NewViewVC { static func create(dependencies: NewViewVM<NewViewVC>.Dependencies) -> NewViewVC { let vc = NewViewVC() let viewModel = NewViewVM.create(dependencies: dependencies, services: .init(), view: vc) vc.viewModel = viewModel return vc } static func createMock(dependencies: NewViewVM<NewViewVC>.Dependencies, services: NewViewVM<NewViewVC>.Dependencies.Services) -> NewViewVC { let vc = NewViewVC() let viewModel = NewViewVM.create(dependencies: dependencies, services: services, view: vc) vc.viewModel = viewModel return vc } }
ViewModel实现
protocol NewViewVMInterface { func reloadData() } final class NewViewVM<V: VMView>: ViewModel, NewViewVMInterface { struct Model {} enum Command { case start, reload } struct Dependencies { struct Services { } } private var view: V! private var dependencies: Dependencies! private var services: Dependencies.Services! static func create(dependencies: Dependencies, services: Dependencies.Services, view: V) -> NewViewVM where V.Model == Model, V.Command == Command { let viewModel = NewViewVM() viewModel.dependencies = dependencies viewModel.services = services viewModel.view = view return viewModel } func reloadData() { // fetch data view.perform(command: .reload) // <- 报错位置 } }
问题根源
报错的核心原因是:NewViewVM类的泛型约束仅指定V: VMView,虽然create方法通过where子句限定了V.Model == Model和V.Command == Command,但这个约束只对create方法内部生效,类的其他方法(比如reloadData)无法感知到V.Command与自身定义的Command枚举是同一类型,因此编译器认为V.Command可能是任意类型,不存在reload成员。
优化解决方案
方案一:将类型约束提升至类级别
直接在NewViewVM的泛型定义中添加类型匹配约束,让整个类内部都能明确V的关联类型与ViewModel自身类型的匹配关系:
final class NewViewVM<V: VMView>: ViewModel, NewViewVMInterface where V.Model == Model, V.Command == Command { struct Model {} enum Command { case start, reload } struct Dependencies { struct Services { } } private var view: V! private var dependencies: Dependencies! private var services: Dependencies.Services! // 此时create方法无需额外where约束 static func create(dependencies: Dependencies, services: Dependencies.Services, view: V) -> NewViewVM { let viewModel = NewViewVM() viewModel.dependencies = dependencies viewModel.services = services viewModel.view = view return viewModel } func reloadData() { view.perform(command: .reload) // 编译器可正确识别类型 } }
方案二:调整ViewModel协议的关联关系
修改ViewModel协议,让它直接关联对应的VMView类型,并默认将自身的Command和Model与View的关联类型绑定,进一步强化类型绑定的清晰度:
// 调整ViewModel协议 protocol ViewModel { associatedtype View: VMView associatedtype Command = View.Command associatedtype Model = View.Model associatedtype Dependencies } // 修改NewViewVM实现 final class NewViewVM<V: VMView>: ViewModel, NewViewVMInterface { typealias View = V struct Model {} enum Command { case start, reload } struct Dependencies { struct Services { } } private var view: V! private var dependencies: Dependencies! private var services: Dependencies.Services! static func create(dependencies: Dependencies, services: Dependencies.Services, view: V) -> NewViewVM { let viewModel = NewViewVM() viewModel.dependencies = dependencies viewModel.services = services viewModel.view = view return viewModel } func reloadData() { view.perform(command: .reload) // 类型匹配关系明确,编译通过 } }
两种方案都能彻底解决编译错误,避免强制类型转换的不优雅写法,同时保证类型安全,让ViewModel与View的关联关系更加清晰。
内容的提问来源于stack exchange,提问作者sam k
相关产品推荐
相关产品推荐

