Swift MVVM+Coordinator 实现
准备
- 环境:macOS Big Sur, Xcode 13.0, Swift 5.0, iOS 13+, bundle, cocoapods
- 技术点: Snapkit, UITableViewDiffableDataSource, Combine
关于Coordinator
Coordinator(协调器)是路由的一种实现方式,类似于树状结构,能够清晰的表明页面的层级结构,同时解除页面间的耦合关系。核心机制就像失物招领,子节点发出需求,对应节点响应需求。
架构
结构图
MVVMC.png
职责说明
- App Coordinator
所有Coordinator的根节点,控制一级页面的展示逻辑,同时监听跨组件跳转需求并分发给对应的Coordinator。 - Tab Coordinator
一级页面根节点,控制本分支下的二级页面展示逻辑。 - Child Coordinator
子节点,控制本节点的页面展示逻辑,可继续延伸。 - ViewModel
业务逻辑层,负责接收View的请求,调用Service或coordinator实现,并将结果通过KVO或其他方式传递给Views - Views
页面渲染层,负责展示UI,实现交互,提供操作输入渠道。 - Services
服务层,负责调用后台API或本地数据库
实现
创建基础Coordinator Protocol
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { set get }
var notificationAction: RouterAction? { set get }
var navigationController: UINavigationController { set get }
@discardableResult func startChildCoordinator(_ context: Router) -> Bool
func start(_ context: Router) -> Bool
}
- childCoordinators: 子节点列表
- notificationAction: 跨组件跳转、通用跳转方法
- navigationController: 导航根controller
- startChildCoordinator: 根据Router进行子节点跳转并返回是否消费此Router
- start: 展示自身,并返回是否消费此Router
添加默认实现方法
extension Coordinator {
func addChildCoordinator(_ childCoordinator: Coordinator) {
guard !childCoordinators.contains(where: { $0 === childCoordinator }) else { return }
childCoordinator.navigationController = self.navigationController
childCoordinator.notificationAction = {[weak self] context in
guard let self = self else { return }
if !self.start(context) {
self.notificationAction?(context)
}
}
childCoordinators.append(childCoordinator)
}
func removeChildCoordinator(_ childCoordinator: Coordinator) {
guard let index = childCoordinators.firstIndex(where: { $0 === childCoordinator }) else { return }
childCoordinators.remove(at: index)
}
@discardableResult func startChildCoordinator(_ context: Router) -> Bool {
for childCoordinator in childCoordinators {
if childCoordinator.start(context) {
return true
}
}
return false
}
}
- addChildCoordinator: 默认设置子节点的navigationController为自身的,设置回调当子节点有无法处理的需求需要上抛时接收,如果自身无法消费则继续往上抛
创建TabCoordinator
protocol TabCoordinator: Coordinator {
var tabBarTitle: String { get }
var tabBarImage: UIImage? { get }
var tabBarSelectedImage: UIImage? { get }
func start()
}
使用
class HomeCoordinator: TabCoordinator {
var notificationAction: RouterAction?
var tabBarTitle: String { "首页" }
var tabBarImage: UIImage? { nil }
var tabBarSelectedImage: UIImage? { nil }
var navigationController: UINavigationController = .init()
var childCoordinators: [Coordinator] = []
func start() {
navigationController.setViewControllers([HomeViewController(coordinator: self)], animated: false)
initChildCoordinators()
}
func initChildCoordinators() {
addChildCoordinator(ListCoordinator())
}
func start(_ context: Router) -> Bool {
guard let router = context as? HomeRouter else { return false }
switch router {
case .home:
start()
return true
default:
return startChildCoordinator(router)
}
}
}
关于Demo中的ListViewController
Demo中实现了Combine与UITableViewDiffableDataSource的基本用法,感兴趣的小伙伴可以看看。