You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单个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模块,通过路由传递回调、数据层持久化选中状态来实现需求:

  1. 状态持久化:把用户选中的行ID/对应数据存储在主模块的Interactor层(或独立的业务数据Store),不要在Presenter或View层存储状态,确保不管模态VC销毁重建多少次,都能拿到最新的选中值。
  2. 跳转逻辑交给主模块Router:主VC需要弹出模态VC时,由主Presenter通知Router初始化、配置独立的Currencies选择模块,同时把当前已选中的值传入Currencies模块,用于页面加载时自动设置高亮。
  3. 回调传值:用户在模态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
    }
}
选中逻辑流程
  1. 主VC触发弹出选择器事件,通知主Presenter
// 主VC
func didTapChooseCurrency() {
    presenter.didRequestCurrencySelection()
}
  1. 主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)
    }
}
  1. 模态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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 09:57:04