IOS架構(gòu)之--使用Coordinator提高VC/ViewModel復(fù)用性

文章結(jié)構(gòu)


1.什么是Coordinator,它為了解決什么問(wèn)題?
2.Coordinator的優(yōu)點(diǎn)

一、什么是Coordinator,它為了解決什么問(wèn)題?


講這個(gè)問(wèn)題之前我們先看一段我們常用的一段跳轉(zhuǎn)代碼,我們有這樣一個(gè)需求
在訂單列表類SKOrderListViewController點(diǎn)擊相應(yīng)的訂單cell跳轉(zhuǎn)到相應(yīng)的訂單詳情頁(yè)SKDetailViewController
我們常常會(huì)向下面這么寫:

SKOrderListViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  
    id object = [self.dataSource objectAtIndexPath:indexPath];  
    SKDetailViewController *detailViewController = [[SKDetailViewController alloc] initWithDetailObject:object];  
    [self.navigationController pushViewController:detailViewController animated:YES];  
}  

在tableview 代理里的cell點(diǎn)擊事件里面先實(shí)例化接下來(lái)要跳轉(zhuǎn)的SKDetailViewController配置它所需要的數(shù)據(jù)最后調(diào)用父navigationController推向下一個(gè)頁(yè)面。

這時(shí)候產(chǎn)品過(guò)來(lái)說(shuō)我們說(shuō),這里我們需要根據(jù)用戶角色的不同類型所展示的詳情頁(yè)的明細(xì)不同。
上面的代碼就會(huì)變成下面這樣:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    id object = [self.dataSource objectAtIndexPath:indexPath];
    if(user.role == userRoleNor){
        SKDetailViewController *detailViewController = [[SKDetailViewController alloc] initWithDetailObject:object];
        [self.navigationController pushViewController:detailViewController animated:YES];
    }else if(user.role == userRoleVip){
        SKVipDetailViewController *detailViewController = [[SKVipDetailViewController alloc] initWithDetailObject:object];
        [self.navigationController pushViewController:detailViewController animated:YES];
    }else if(user.role == userRoleMannage){
        SKManDetailViewController *detailViewController = [[SKManDetailViewController alloc] initWithDetailObject:object];
        [self.navigationController pushViewController:detailViewController animated:YES];
    }
    
}

多個(gè)if-else組合判定各種不同的角色類型,再跳轉(zhuǎn)到不同的應(yīng)用界面。
SKOrderListViewController復(fù)用的越多,這樣的判斷代碼就會(huì)也多,在更加復(fù)雜的情況我們甚至需要遍歷navigationController控制器數(shù)組找到指定的VC來(lái)彈出當(dāng)前的控制器或者切換到具體控制器。
VC/ViewModel變的越來(lái)越繁重,界面邏輯跳轉(zhuǎn)的代碼也變的越來(lái)越復(fù)雜。

我們開(kāi)始思考:

1.頁(yè)面跳轉(zhuǎn)這層邏輯到底是不是VC或ViewModel需要關(guān)注的問(wèn)題或者說(shuō)是不是他們的職責(zé),畢竟VC和ViewModel已經(jīng)負(fù)責(zé)了太多的工作?

2.子控制器命令父導(dǎo)航控制器跳轉(zhuǎn)邏輯上是否合理?如果把這種頁(yè)面的跳轉(zhuǎn)相比成我們實(shí)際工作中任務(wù)角色的切換,那么這種方式就像你完成了你的任務(wù)后命令你的boss讓B員工來(lái)處理。是不是應(yīng)該相反你只要關(guān)注你自己的工作內(nèi)容你需要什么工具需要什么材料,讓boss提供,然后完成任務(wù)后告訴boss,而具體后面相關(guān)的任務(wù)Boss指派給誰(shuí),那都不是我們的事情了?

3.上面討論的只是在iphone設(shè)備上,如果這時(shí)項(xiàng)目經(jīng)理提出項(xiàng)目需要同時(shí)兼容ipad ???

4.設(shè)計(jì)模式MVVM -- 按照架構(gòu)規(guī)定,應(yīng)該是由ViewModel負(fù)責(zé)跳轉(zhuǎn)邏輯(當(dāng)然了ViewModel負(fù)責(zé)的工作遠(yuǎn)比這多),而因?yàn)轫?yè)面跳轉(zhuǎn)只能通過(guò)VC,這使得我們不得不在需要跳轉(zhuǎn)的時(shí)候?qū)iewModel跳轉(zhuǎn)事件通知給VC(也就是View層),或者弱引用當(dāng)前的VC, 完成跳轉(zhuǎn),看起來(lái)各種奇怪。同時(shí)也面臨著MVC Controller層一樣的困境,復(fù)用性低。

綜合上面問(wèn)題,我們?nèi)绻烟D(zhuǎn)邏輯剝離出來(lái),單獨(dú)用一個(gè)類(Coordinator)去管理。而VC/ViewModel只需要關(guān)注他需要做的工作需要什么數(shù)據(jù),完成自己的任務(wù)后通知Coordinator,我完成任務(wù)了。Coordinator收到具體的消息后,配置后面需要跳轉(zhuǎn)的VC/ViewModel的數(shù)據(jù),并推向下一個(gè)界面。甚至說(shuō)具體的用戶類型判斷機(jī)型判斷都可以寫到這個(gè)里面,讓Coordinator去處理。這時(shí)VC/ViewModel 就像是樂(lè)高積木中的積木塊,怎么搭,那就完全取決于你了。
這就是Coordinator也有人稱之為FlowControllers。

1.1 Coordinate與VC/ViewModel 之間的關(guān)系

如下所示:


實(shí)線代表: ViewModel/VC 向外提供的接口(屬性或方法集合)
虛線代表: ViewModel/VC 的跳轉(zhuǎn)事件

這里注意到Coordinate 也有相應(yīng)的實(shí)線和虛線,這里我們稍后再講。我們結(jié)合代碼看下Coordinate與VC之間的聯(lián)系:
demo來(lái)自WRAP-UP: IOS MEETUP WITH DEPENDENCY INJECTION AND FLOW CONTROLLERS FlowController章節(jié)
iOS-FlowControllerDemo:

工程功能相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,就不細(xì)分析了,提出幾個(gè)關(guān)鍵的部分我們看下Coordinate與VC之間是怎么聯(lián)系的(ViewModel類似)。

FlowViewController.swift

class FlowViewController {
    
    // MARK: - Properties
    
    var newEvent: Event //控制器所需要數(shù)據(jù)
    
    var navigationController: UINavigationController!//navigationController
    
    var didFinishBlock: ((FlowViewController) -> Void)?//block或者代理傳遞事件給父FlowViewController調(diào)用。
    
    
    // MARK: - Object Lifecycle
    
    required init(_ navigationController: UINavigationController)
    {
        self.navigationController = navigationController
        
        self.newEvent = Event()
    }
    
    
    // MARK: - Action 跳轉(zhuǎn)相關(guān)函數(shù)
    // Coordinator 初始化頁(yè)
    func start()
    {
        showLocationScreen()
    }
    
    func showLocationScreen()
    {
        let storyboard = UIStoryboard(name: "Location", bundle: nil)
        
        if let controller = storyboard.instantiateInitialViewController() as? LocationViewController {
            
            controller.delegate = self
            
            navigationController.pushViewController(controller, animated: true)
        }
    }
    
    .....................

    func showSummaryScreen()
    {
        let storyboard = UIStoryboard(name: "Summary", bundle: nil)
        
        if let controller = storyboard.instantiateInitialViewController() as? SummaryViewController {
            
            controller.delegate = self
            
            controller.newEvent = newEvent
            
            navigationController.pushViewController(controller, animated: true)
        }
    }
    
    func finish()
    {
        navigationController.popToRootViewController(animated: true)
        
        didFinishBlock!(self)
    }

   // MARK: - delegate VC跳轉(zhuǎn)事件回調(diào)
extension FlowViewController: SummaryViewControllerDelegate {
    
    func controllerDidConfirm(controller: SummaryViewController) {
        
        finish()
    }
    
    func controllerDidCancel(controller: SummaryViewController) {
        
    }
}

SummaryViewController.swift


import UIKit

protocol SummaryViewControllerDelegate: class {
    
    func controllerDidConfirm(controller: SummaryViewController)
    
    func controllerDidCancel(controller: SummaryViewController)
}

class SummaryViewController: UIViewController {

    // MARK: - Data
    
    var newEvent: Event!

   func configureView() {
        
        lblLocationValue.text = newEvent.location
        
        let dateFormatter = DateFormatter()
        
        dateFormatter.dateStyle = .medium
        
        lblDateValue.text = dateFormatter.string(from: newEvent.date!)
    }
    
    
    // MARK: - Action
    
    func doneAction() {
        
        didConfirm()
    }

SummaryViewController 向Coordinator提供數(shù)據(jù)接口(在這里是newEvent屬性),并定義跳轉(zhuǎn)方法協(xié)議(SummaryViewControllerDelegate)。Coordinator則負(fù)責(zé)配置SummaryViewController所需的Event數(shù)據(jù),并響應(yīng)SummaryViewController跳轉(zhuǎn)協(xié)議方法。

我們?cè)俳獯鹣律厦嫣岬降臑槭裁碈oordinate也有類似于VC一樣實(shí)線虛線,
看下Coordinate 在整個(gè)工程的結(jié)構(gòu),自然就明白了:


貼幾個(gè)關(guān)鍵性的代碼就不細(xì)講了:
工程地址:
mvvmc-demo

AppCoordinator.swift

class AppCoordinator: Coordinator
{
    fileprivate let AUTHENTICATION_KEY: String  = "Authentication"
    fileprivate let LIST_KEY: String  = "List"

    var window: UIWindow
    var coordinators = [String:Coordinator]()//子Coordinator數(shù)組
    
    init(window: UIWindow)
    {
        self.window = window
    }
    
    func start()
    {
        if isLoggedIn {
            showList()
        } else {
            showAuthentication()
        }
    }

    func showList()
    {
        let listCoordinator = ListCoordinator(window: window)
        coordinators[LIST_KEY] = listCoordinator
        listCoordinator.delegate = self
        listCoordinator.start()
    }

   func showAuthentication()
    {
        let authenticationCoordinator = AuthenticationCoordinator(window: window)
        coordinators[AUTHENTICATION_KEY] = authenticationCoordinator
        authenticationCoordinator.delegate = self
        authenticationCoordinator.start()
    }
}

extension AppCoordinator: AuthenticationCoordinatorDelegate
{
    var isLoggedIn: Bool {
        return false;
    }

    func authenticationCoordinatorDidFinish(authenticationCoordinator: AuthenticationCoordinator)
    {
        coordinators[AUTHENTICATION_KEY] = nil
        showList()
    }
}
AppDelegate.swift
import UIKit

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate
{
    var window: UIWindow?
    var appCoordinator: AppCoordinator!

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
    {
        
        window = UIWindow()
        appCoordinator = AppCoordinator(window: window!)
        appCoordinator.start()
        window?.makeKeyAndVisible()
        
        return true
    }
}

最后再引入一個(gè)寫了單元測(cè)試的MVVM-C的工程,方便后期查看
https://github.com/digoreis/ExampleMVVMFlow

2.Coordinator的優(yōu)點(diǎn)


1.VC/ViewModel的復(fù)用性提高
2.VC/ViewModel之間相對(duì)獨(dú)立,單個(gè)VC/ViewModel可測(cè)試性強(qiáng),提高單元測(cè)試的覆蓋率。
3.具體應(yīng)用場(chǎng)景,App跳轉(zhuǎn)情況全部在Coordinator中,VC/ViewModel積木怎么搭完全取決于你。

參考文獻(xiàn):
Improve your iOS Architecture with FlowControllers
Coordinators Redux
Coordinator and FlowController
An iOS Coordinator Pattern
MVVM-C A simple way to navigate
MVVM with Flow Controller
MVVM and Tests

工程參考:

mvvmc-demo
iOS-FlowControllerDemo
ExampleMVVMFlow

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容