在开始阅读这篇文章之前,建议你先 学习 RxSwift。
内容概览
- Coordinator模式
- MVVM模式
- RxSwift with Coordinator & MVVM Patterns(内含Demo)
Coordinator模式
你有没有遭遇过这种情况?
你的ViewController之间相互耦合、依赖,导航逻辑分布散乱。
你是否听过 Massive View Controller问题?
在MVC模式下,ViewController做了过多的工作,比如:初始化代码、界面跳转逻辑、业务逻辑等。
其中就一项任务本不该由ViewController来完成:屏幕导航的管理和应用的执行流程。
- 什么是Coordinator模式?
Coordinator模式提供了导航逻辑的封装,由Soroush Khanlou在2015年的NSSpain会议上引入iOS社区。
换句话说,所有的屏幕导航将由Coordinator来管理,而不是让某个ViewController通过push或者present方法来显示另一个ViewController。
-
采用Coordinator模式有什么好处?
采用Coordinator模式,ViewController之间被隔离开来,而且彼此不可见。
这样,就可以很方便地复用ViewController。
正如上面的图解所示,Coordinator模式的核心思想可以归纳为以下几点:
- 通过Coordinator协调一个或多个ViewController;
- 每个Coordinator通过方法(通常叫作start方法)来显示ViewControllers;
- 每个ViewController都有一个delegate引用指向其Coordinator;
- 每个Coordinator有一个childCoordinators数组来持有其子Coordinator;
- 每个字Coordinator都有一个delegate引用指向其父Coordinator;
使用示例
Coordinator模式的实现有比较的方式,所以这里的示例仅供参考。
这里采用两个Coordinator来管理3个ViewController,以此展示一个Coordinator可以有一个或多个ViewController。
首先,创建3个ViewController,FirstViewController, SecondViewController and ThirdViewController。
每个ViewController添加一个Button来导航到下一个ViewController。
首先,我们创建Coordinator协议。
协议包含一个childCoordinators数组和一个只接收NavigationController类型参数的init方法。
public protocol Coordinator : class {
var childCoordinators: [Coordinator] { get set }
// All coordinators will be initilised with a navigation controller
init(navigationController:UINavigationController)
func start()
}
然后第一个Coordinator会管理第一个ViewController。实现start方法,把第一个ViewController添加到NavigationController中。
import UIKit
class FirstCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
unowned let navigationController:UINavigationController
required init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let firstViewController : FirstViewController = FirstViewController()
firstViewController.delegate = self
self.navigationController.viewControllers = [firstViewController]
}
}
extension FirstCoordinator: FirstViewControllerDelegate {
// Navigate to next page
func navigateToNextPage() {
let secondCoordinator = SecondCoordinator(navigationController: navigationController)
secondCoordinator.delegate = self
childCoordinators.append(secondCoordinator)
secondCoordinator.start()
}
}
extension FirstCoordinator: BackToFirstViewControllerDelegate {
// Back from third page
func navigateBackToFirstPage(newOrderCoordinator: SecondCoordinator) {
navigationController.popToRootViewController(animated: true)
childCoordinators.removeLast()
}
}
FirstCoordinator有两个扩展,第一个用于导航到下一个ViewController,第二个是导航回FirstCoordinator。
我们需要让childCoordinators数组和当前的Coordinator栈保持同步。
var window: UIWindow?
// Make the first coordinator with a strong reference
var fisrtCoordinator : FirstCoordinator?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController()
// Initialise the first coordinator with the main navigation controller
fisrtCoordinator = FirstCoordinator(navigationController: window?.rootViewController as! UINavigationController)
// The start method will actually display the main view
fisrtCoordinator?.start()
window?.makeKeyAndVisible()
return true
}
然后,以navigationController作为参数创建fisrtCoordinator。让fisrtCoordinator的start来负责展示firstViewController。
public protocol FirstViewControllerDelegate: class {
func navigateToNextPage()
}
class FirstViewController: UIViewController {
public weak var delegate: FirstViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
title = "FirstViewController"
}
@IBAction func goToSecondPageAction(_ sender: Any) {
self.delegate?.navigateToNextPage()
}
}
现在,为了导航到下一个ViewController,我们需要通过delegate告诉Coordinator进行导航。
protocol BackToFirstViewControllerDelegate: class {
func navigateBackToFirstPage(newOrderCoordinator: SecondCoordinator)
}
class SecondCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
unowned let navigationController: UINavigationController
// We use this delegate to keep a reference to the parent coordinator
weak var delegate: BackToFirstViewControllerDelegate?
required init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let secondViewController: SecondViewController = SecondViewController()
secondViewController.delegate = self
self.navigationController.pushViewController(secondViewController, animated: true)
}
}
extension SecondCoordinator : SecondViewControllerDelegate {
// Navigate to third page
func navigateToThirdPage() {
let thirdViewController: ThirdViewController = ThirdViewController()
thirdViewController.delegate = self
self.navigationController.pushViewController(thirdViewController, animated: true)
}
// Navigate to first page
func navigateToFirstPage() {
self.delegate?.navigateBackToFirstPage(newOrderCoordinator: self)
}
}
正如前文所述,一个Coordinator可以管理一个或多个ViewController。所以,这里让secondCoordinator管理secondViewController 和 thirdViewController。
public protocol SecondViewControllerDelegate: class {
func navigateToFirstPage()
func navigateToThirdPage()
}
class SecondViewController: UIViewController {
public weak var delegate: SecondViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
title = "SecondViewController"
// Use custom back button to pass through coordinator.
let backButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(navigateBackToFirstpage))
self.navigationItem.leftBarButtonItem = backButton
}
@objc func navigateBackToFirstpage() {
self.delegate?.navigateToFirstPage()
}
@IBAction func navigateToThirdPageAction(_ sender: Any) {
self.delegate?.navigateToThirdPage()
}
}
请注意:
在切换Coordinator时,NavigationController的返回按钮的点击事件方法如果没有被重写的话,就会破坏Coordinator模式的逻辑。我们应该重写NavigationController返回按钮的点击事件方法来执行Coordinator中的方法,以保证childCoordinators数组正常更新。
这里是ThirdViewController的实现:
public class ThirdViewController: UIViewController {
public weak var delegate: SecondViewControllerDelegate?
override public func viewDidLoad() {
super.viewDidLoad()
title = "ThirdViewController"
}
@IBAction func navigateToFirstPageAction(_ sender: Any) {
self.delegate?.navigateToFirstPage()
}
}
最后,ThirdViewController调用代理方法可以回到FirstViewController。
结论
现在,我们可以通过Coordinator来管理ViewController之间的导航,不需要ViewController之间有任何相互引用。
将导航逻辑移到Coordinator中,有效地削弱了Massive ViewController效应。
MVVM模式
- 什么是MVVM模式?
Model:数据模型 或 数据持久层;
View:用户交互界面层;
ViewModel:描述模型数据的状态,负责转换Model中的数据;
Binder:绑定View和ViewModel中对应的属性;
- 采用MVVM模式有什么好处?
利于团队合作:设计师可以专注于用户界面,开发者可以专注于业务逻辑。感兴趣的话,请搜索 WPF MVVM;
利于代码重用:模型、视图被解耦,更容易复用;
利于自动化测试:模块耦合度低,模块化测试容易实施;
RxSwift with Coordinator & MVVM
Demo采用了Coordinator模式和MVVM模式,部分代码拷贝于RxSwift官方Demo。
此Demo仅供参考,欢迎读者提出宝贵的建议,谢谢。
参考文章:
iOS : Coordinator pattern in Swift
Coordinator Tutorial for iOS: Getting Started (Raywenderlich Demo)
3 reasons to use the MVVM pattern
Coordinator 与 RxSwift 共同构建的 MVVM 架构
Observe Changes on TableView Cell with RxSwift and RxCocoa
如需转载,请注明出处,谢谢 ~