单个VIPER模块集成多个ViewController的实现方案合规性咨询
现有方案存在的问题
你的当前实现不符合VIPER的核心设计原则,能正常运行属于巧合,后续会存在大量隐患:
- 你在两个VC的
viewDidLoad中调用配置方法时,每次都会新建另一个VC的实例、以及全新的Presenter/Interactor/Router实例。主VC中创建的模态VC实例和你实际弹出的模态VC不是同一个内存对象,模态VC中创建的主VC实例也和实际的主VC完全独立,后续必然会出现状态不同步、内存泄漏的问题。 - VIPER要求每个模块的组件遵循单一职责原则,单个Presenter只负责对应单个View的业务逻辑。你将两个VC绑定到同一个Presenter,还让Presenter同时持有主View和模态View,会导致Presenter职责过重,两个页面的逻辑完全耦合,后续维护成本极高。
- 如果你后续需要修改模态VC的弹出逻辑,或者复用这个选择页面到其他业务场景,当前的共享Presenter架构完全无法支持。
符合VIPER规范的优化方案
你可以将两个页面拆分为独立的VIPER模块,通过路由传递回调、数据层持久化选中状态来实现需求:
- 状态持久化:把用户选中的行ID/对应数据存储在主模块的Interactor层(或独立的业务数据Store),不要在Presenter或View层存储状态,确保不管模态VC销毁重建多少次,都能拿到最新的选中值。
- 跳转逻辑交给主模块Router:主VC需要弹出模态VC时,由主Presenter通知Router初始化、配置独立的Currencies选择模块,同时把当前已选中的值传入Currencies模块,用于页面加载时自动设置高亮。
- 回调传值:用户在模态VC选中行后,通过初始化时传入的闭包/代理把选中值传回主模块,主Presenter更新Interactor的存储状态后同步更新主VC的UI即可。
核心代码示例
主模块Router跳转逻辑
// 主模块Router负责模态VC的初始化、配置、弹出 func presentCurrenciesSelector(selectedId: String, completion: @escaping (String) -> Void) { let currenciesVC = CurrenciesViewController() // 调用模态VC独立的Configurator完成配置 CurrenciesConfigurator.shared.configure( view: currenciesVC, currentSelectedId: selectedId, selectCompletion: completion ) mainViewController.present(currenciesVC, animated: true) }
模态VC独立Configurator实现
protocol CurrenciesConfiguratorProtocol { func configure( view: CurrenciesViewController, currentSelectedId: String, selectCompletion: @escaping (String) -> Void ) } class CurrenciesConfigurator: CurrenciesConfiguratorProtocol { func configure( view: CurrenciesViewController, currentSelectedId: String, selectCompletion: @escaping (String) -> Void ) { let presenter = CurrenciesPresenter( view: view, selectedId: currentSelectedId, selectCallback: selectCompletion ) let interactor = CurrenciesInteractor(presenter: presenter) let router = CurrenciesRouter(view: view) view.presenter = presenter presenter.interactor = interactor presenter.router = router } }
选中逻辑流程
- 主VC触发弹出选择器事件,通知主Presenter
// 主VC func didTapChooseCurrency() { presenter.didRequestCurrencySelection() }
- 主Presenter读取Interactor存储的当前选中值,调用Router跳转
// 主Presenter func didRequestCurrencySelection() { let currentSelected = interactor.currentSelectedCurrencyId router.presentCurrenciesSelector(selectedId: currentSelected) { [weak self] newSelectedId in guard let self = self else { return } // 更新数据层存储 self.interactor.updateSelectedCurrency(id: newSelectedId) // 更新主VC UI self.view.updateCurrencyDisplay(id: newSelectedId) } }
- 模态VC选中行后通知自身Presenter,执行回调关闭页面
// 模态VC func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selected = currencyList[indexPath.row] presenter.didSelectCurrency(id: selected.id) } // 模态Presenter func didSelectCurrency(id: String) { selectCallback(id) router.dismissSelector() }
优化后的优势
- 完全符合VIPER单一职责原则,两个模块完全解耦,模态选择器可复用到其他业务场景
- 选中状态持久化在数据层,模态VC每次弹出都能拿到最新选中值,自动恢复高亮
- 不存在冗余的实例创建,不会出现内存泄漏、状态不同步的问题
内容的提问来源于stack exchange,提问作者MaryLitv21
相关产品推荐
相关产品推荐

