在iOS上使用MVVM进行路由

我在多个项目中使用MVVM已有一段时间了,我非常喜欢它的简单性。 特别是,如果您像许多人一样从MVC移出,则只需要在体系结构中增加一层即可; 查看模型。 如果您发现太多复杂的图层,这确实使事情变得容易。

这是一个好的开始,但是这种简单性并不总是很好。 在MVVM中,将业务逻辑移出视图控制器(VC),然后意识到它仍然很胖。 视图模型(VM)现在具有业务逻辑,但是表示数据(格式)或路由又如何呢? 它们仍然停留在VC中,我们需要将它们移出。

样品流量

假设我们正在实现一个登录屏幕,如下所示。

路线清单:

  • 登录>主屏幕
  • 注册>注册屏幕
  • 忘记密码(?)>忘记密码屏幕

这似乎是一个简单的屏幕,可以使用带有3个脚本的情节提要实现。 但是请相信我,事实并非如此。 例如,您通常会在登录时打开主屏幕。 但是在这种情况下,用户的密码可能已过期,您需要实现重定向以更改密码屏幕。 因此,登录路径变为:

  • 登录>主屏幕更改密码屏幕

这是情节提要路由失败的地方。 它只是无法应付这种动力。 因此,通常要做的就是让VC处理它:

  func loginButtonTapped(){ 
//开始网络请求...
//回应后:
如果viewModel.shouldChangePassword {
performSegue(id:“ ChangePasswordScreen”,发送者:nil)
}其他{
performSegue(id:“ HomeScreen”,发送者:nil)
}
}

这是路由逻辑,不应在VC中使用。 如果要使用轻量级VC,请在编写if语句之前三思。 他们是决定,不属于那里。 以我的理解,VC仅应具有与视图相关的代码和粘合代码。 永远没有决定。

让我们定义一个路由器协议,并将这些if语句从VC中删除。 我们会需要:

  • 路由ID :字符串标识符,例如segue ID。
  • 上下文 :要从其路由的当前视图控制器。
  • 可选参数 :过渡所需的临时数据。 (点击的行索引等)
 协议路由器{ 
功能路线(
到routeID:字符串,
来自上下文:UIViewController,
参数:有吗?

}

VC仅应定义路由名称,而不关心该路由指向何处。 那将是路由器的工作。

 类LoginViewController:UIViewController { 
 枚举路线:字符串{ 
案例登录
案例注册
忘记密码的情况
}
  var viewModel:LoginViewModel! 
var router:路由器!
  ... 
  func loginButtonTapped(){ 
router.route(发送至:Route.login.rawValue,来自:self)
}
  func signUpTapped(){ 
router.route(发送至:Route.signUp.rawValue,来自:self)
}
  func forgotPasswordTapped(){ 
router.route(发送至:Route.forgotPassword.rawValue,来自:self)
}
  } 

如前所述,登录按钮可以进入主屏幕或更改密码屏幕。 那么路由器将如何选择正确的目的地? 在这种情况下,您的路由器可能需要访问您的VM。 这样,它可以直接读取业务决策并确定目的地。

注意,VC已经保留了VM和路由器。 因此,路由器 对VM的引用 弱/无名

 类LoginRouter:路由器{ 
 无主var viewModel:LoginViewModel 
  init(viewModel:LoginViewModel){ 
self.viewModel = viewModel
}
 功能路线( 
到routeID:字符串,
来自上下文:UIViewController,
参数:可以吗?)
{
保护让路由= LoginVC.Route(rawValue:routeID)否则{
返回
}
切换路线{
案例.login:
如果viewModel.shouldChangePassword {
//按下更改密码屏幕。
}其他{
//推送主屏幕。
}
案例.signUp:
//推送注册屏幕:
让vc = SignUpViewController()
让vm = SignUpViewModel()
vc.viewModel = vm
vc.router = SignUpRouter(viewModel:vm)
context.navigationController.push(vc,动画:true)
案件 。 forgotPasswordScreen:
//按“忘记密码”屏幕。
}
}
}

底线

  • 我们将路由代码完全移出了VC。 这有利于分离关注点。 如果路由逻辑发生变化,则只需编辑路由器,而不是在VC中搜索push / present语句。
  • 随着时间的推移,您将获得许多设计更改。 因此,使视图控制器保持明亮很重要,因为它与视图紧密耦合。 您不希望在进行UI大修时破坏路由逻辑。
  • 您不必通过这种方法使用情节提要剧集。 我不知道我是否伤了您的心,但您无法通过segues实现这种动态流程。 故事板仅应负责布局(再次,关注点分离)。