iOS 組件化-路由解耦思想 JLRoutes 實(shí)戰(zhàn)篇(一)App內(nèi)控制器跳轉(zhuǎn)

Router

前言

組件化, Router 這些概念可能在幾年前還是比較新穎的概念, 至今相信絕大多數(shù)同學(xué)都對(duì)這些名詞已耳熟能詳, 筆者在真正接觸到 Router 并在項(xiàng)目使用之前, 也有讀過一些 組件化, Router 進(jìn)行解耦的思想和框架的文章, 但是由于自己修行不夠, 加上沒能真正將其運(yùn)用到項(xiàng)目進(jìn)行實(shí)踐。 導(dǎo)致每次讀完文章之后, 所理解的知識(shí)沒能真正轉(zhuǎn)化為可以解決問題的技能, 筆者有幸在項(xiàng)目中接觸并運(yùn)用著名開源庫 JLRouter 來解決 App 內(nèi)外所有頁面之間的跳轉(zhuǎn)邏輯, 經(jīng)過這幾年的學(xué)習(xí)和使用, 將其記錄一邊鞏固知識(shí), 寫出來跟大家一起學(xué)習(xí), 加上看到網(wǎng)上分享關(guān)于使用組件化-Router 相關(guān)文章偏于理論, 很少有完整詳細(xì)Demo, 具體在項(xiàng)目中使用還需進(jìn)一步深入研究, 所以有了此篇文章, 有什么不對(duì)或需要補(bǔ)充的, 望大家多多指教舷蒲。
此篇文章偏向?qū)崙?zhàn), 想深入學(xué)習(xí) Router 思想的推薦霜神寫的 iOS 組件化 —— 路由設(shè)計(jì)思路分析蛤克。

==Demo 在文章最??==

為什么 Router

路由基礎(chǔ)三問, 每次接觸新穎思想框架時(shí), 我都會(huì)不禁的問自己這幾個(gè)問題, 希望通過下面幾個(gè)簡(jiǎn)要的概括, 能很好的幫助大家理解 Router;

  • 路由是什么,解決了什么問題

引用霜神Blog 圖片

上面一幅圖很形象的展示了項(xiàng)目中各個(gè)控制器模塊之間錯(cuò)綜復(fù)雜的關(guān)系, 當(dāng)我們?cè)谔幚聿划?dāng)?shù)那闆r下可能更加糟糕.
使用 Router 之后大概是這樣的;
Router

打個(gè)比方, Router 就是跟我們?nèi)粘J褂玫穆酚善饕粯? App 內(nèi)每個(gè)控制器可以想象成已經(jīng)連接了這臺(tái)路由器的不同設(shè)備, 當(dāng)然連接路由器時(shí), 一般需要輸入密碼, Router 同樣的, 使用前需要每臺(tái)設(shè)備進(jìn)行一次注冊(cè), Router 在內(nèi)部保存每臺(tái)設(shè)備的 URL, 不同設(shè)備之間需要交互時(shí), 將消息發(fā)送到路由器中統(tǒng)一處理;
當(dāng)控制器之間需要交互跳轉(zhuǎn)時(shí), 只需要將對(duì)應(yīng)的 URL 地址發(fā)送到 Router 里, Router 根據(jù)其注冊(cè)的 URL 來尋址到對(duì)方信息, 然后負(fù)責(zé)實(shí)例化對(duì)象, 并傳參, 進(jìn)行跳轉(zhuǎn)等工作, 各個(gè)控制器之間不需要相互依賴對(duì)方, 完美解決不同模塊之間耦合慢蜓!

  • 為什么要用路由來實(shí)現(xiàn) VC 跳轉(zhuǎn)
    Router 能做的事情很多, 首先我們用它來解決棘手的控制器耦合關(guān)系,是一種非常有效的解決方案;
    在 App 中控制器跳轉(zhuǎn)普遍分為 3 種, 模態(tài)跳轉(zhuǎn)Modal(presented/dismiss), 導(dǎo)航控制器跳轉(zhuǎn)(Push/pop), Storyboard 跳轉(zhuǎn)(Segue), 還有 UITabBarVC 主控制器 Index 切換;
    除了常規(guī)的控制器之間跳轉(zhuǎn)之外, 還會(huì)有 3D Touch 指定跳轉(zhuǎn)到某個(gè)控制器中;
    App 之間跳轉(zhuǎn): URL Scheme, Universal Links方式;
    可想而知 App 內(nèi)不管是頁面切換, 外部調(diào)用, 都會(huì)涉及到控制器的跳轉(zhuǎn), 切換等等;
    下面引用常見場(chǎng)景來舉個(gè)栗子:
    Router 前 偽代碼:

假如在沒有引入 Router 之前, 實(shí)現(xiàn) A Push B, B Modal C 的場(chǎng)景: 一般做法都是在 A 中引入B, B 中引入 C, 然后在每次跳轉(zhuǎn)前都需要來一段硬編碼,

//A Push B   A 頁面跳轉(zhuǎn)至 B頁面, 并且設(shè)置相應(yīng) @perpeoty, callback 等;
#import "B"
B* BVC = [B new]; 
BVC.delegate = A;
BVC.name = @"jersey";
BVC.callback = ^ (id data) {
};
... 
...
... 對(duì) b 設(shè)置一些業(yè)務(wù)相關(guān)參數(shù), delegate,  callback 等等;
[A.nav pushVC: BVC animation: true]; 
// B -> C 
#import "C"
C* CVC = [C new];
[B presentVC: CVC];
[B presentVC: CVC animation: true completion: nil];

==Router 后 偽代碼:==

在引用了 Router 之后, 相同的場(chǎng)景下, 我們的代碼是這樣的; 在需要做跳轉(zhuǎn)的控制器引入我們封裝好的 ==JSDVCRouter(是針對(duì) JLRouter 進(jìn)行的一層封裝, 專門用于管理 App 跳轉(zhuǎn)的類, 在文章后面會(huì)詳細(xì)講解)== 即可.

    // A Push B;
    #import "JSDVCRouter"
    [JSDVCRouter openURL: BVCPath info: @{@"delegate":self,@"name":@"jersey",@"callback":callback}];    
    // BVCPath: 表示我們對(duì) B 控制器定義的路徑, 一般保存在全局 Map 里面, 每個(gè) Path 映射當(dāng)前控制器 Map 包含相關(guān) title, class, needLog, 等參數(shù);
    // B Modal C
    [JSDVCRouter openURL: C info: {kJSDRouteSegue: @"Modal"}]; // 控制器之間跳轉(zhuǎn)默認(rèn)以 Push 實(shí)現(xiàn), 當(dāng)需要 Modal 時(shí), 則傳遞一個(gè)參數(shù); 

看到這里相信認(rèn)真閱讀的同學(xué)們已看出使用 Router 的好處:
1. 耦合度降低: A 控制器不需要知道 B 控制器的存在, 只需要 import "JSDRouter", 由其去進(jìn)行相應(yīng)跳轉(zhuǎn)邏輯, 以及賦值等等;
2. 代碼閱讀性提高: 當(dāng)然在剛剛接觸時(shí), 看著會(huì)不大不習(xí)慣, 等接觸一段時(shí)間之后, 不僅減少了代碼行數(shù), 同時(shí)可讀性還是很高的, 跟 push/pop, present/dismiss 說再見吧;
3. 提高代碼復(fù)用性: 每次控制器之間跳轉(zhuǎn)和賦值等操作, 都需要重復(fù)性的 code 一次(嚴(yán)重違背了: 可復(fù)用性原則), 通過 JSDRouter 將跳轉(zhuǎn)和賦值等邏輯封裝起來, 一次 code, 終生受用;
4. 易于維護(hù): 寫到這一點(diǎn)有點(diǎn)兒糾結(jié), 當(dāng)項(xiàng)目隨著公司規(guī)模不斷壯大時(shí), 控制器數(shù)量, 跳轉(zhuǎn)變得越加復(fù)雜, 跳轉(zhuǎn)方法和邏輯很容易變得越來越混亂, 后期管理起來比較困難。 使用 JSDVCRouter 單一職責(zé)的原則來專門負(fù)責(zé) App 內(nèi)所有的跳轉(zhuǎn), 能非常有效的提高測(cè)試及后期維護(hù), 當(dāng)然成本是需要維護(hù) RouterMap 同時(shí)完善 JSDVCRouter 內(nèi)部邏輯;
5. 動(dòng)態(tài)化及靈活性: 使用 Router 時(shí)可以配合后臺(tái)響應(yīng)傳遞響應(yīng)的 Key 來決定真正跳轉(zhuǎn)的頁面, 而不是硬編碼的方式來進(jìn)行跳轉(zhuǎn);
6. 待補(bǔ)充:

  • 實(shí)現(xiàn) Router 完成控制器跳轉(zhuǎn), 至少需要幾個(gè)步驟畏鼓?
    首次將控制器跳轉(zhuǎn)轉(zhuǎn)成 Router 方案
    很簡(jiǎn)單只有 3個(gè)步驟, 如何需求變動(dòng)不大的話, 幾乎一勞永逸拘悦;
  1. Map 表創(chuàng)建: 其是一個(gè)全局 Map, App 內(nèi)相應(yīng)的控制器定義好 Path, Router 可以根據(jù) Path 映射相應(yīng)控制器制定的 Map 內(nèi), Map 里面最少包含當(dāng)前控制器的參數(shù)如: {@"Class": @"控制器類名"}。相當(dāng)于調(diào)用這個(gè)路由時(shí),得到一組其綁定的 Map 作為參數(shù), 通過 Class 來初始化實(shí)例;
    代碼結(jié)構(gòu)如:
     + (NSDictionary *)configInfo
        return @{ JSDRouteHomeCenter: @{
                     @"class": @"JSDAHomeCenterVC",
                     @"name": @"首頁",
                     @"parameter": @"",
                     @"needLogin": @"0", },
                  JSDRouteUserLogin: @{
                     @"class": @"JSDUserLoginVC",
                     @"name": @"登陸",
                     @"parameter": @"",
                     @"needLogin": @"0", },
        };
  1. 封裝 JLRouter; 為了方便使用,管理,以及后期遷移等!類似使用 AFNetwork, SDWebImage, MJRefresh 等有名的開源庫一樣, 由于開源庫提供功能非常豐富, 但是可能我們實(shí)際使用到的只是它一兩個(gè)主要的功能來解決項(xiàng)目中存在的問題, 大家都會(huì)根據(jù)公司具體的業(yè)務(wù)場(chǎng)景或者使用習(xí)慣, 來對(duì)其進(jìn)行一層甚至多層封裝一樣, 使其能更加適合實(shí)際要求;
    筆者對(duì)其進(jìn)行了一層封裝 + Category 的形式: JSDVCRouter,JSDVCRouter + Add;
    JSDVCRouter: 主要用于聲明 Router 調(diào)用接口;
    JSDVCRouter + Handle: 主要用于實(shí)現(xiàn) Router 注冊(cè), 處理控制器之間跳轉(zhuǎn)和參數(shù)賦值代碼;
  2. 根據(jù)約定 Path 進(jìn)行跳轉(zhuǎn): 上面 1 2 都準(zhǔn)備好之后, 即可輕松的進(jìn)行控制器跳轉(zhuǎn) [JSDVCRouter openURL:BVC];

業(yè)務(wù)變更后期維護(hù)

  1. Map 維護(hù): 隨著業(yè)務(wù)發(fā)展, 當(dāng)有新的頁面加入時(shí), 對(duì) Map 添加一個(gè)指定的 Path 和綁定的相應(yīng)參數(shù);
  2. JSDVCRouter 維護(hù): 其包含著真正對(duì)控制器初始化跳轉(zhuǎn)和賦值的代碼這里一般很少進(jìn)行修改; 比如后期需支持跳轉(zhuǎn)到 H5, 處理 3D Touch, Universal Links 時(shí)來這里進(jìn)行維護(hù);

實(shí)戰(zhàn) Code组题!

寫到這里, 筆者不知道上面講的對(duì) Router 實(shí)現(xiàn)控制器跳轉(zhuǎn)的簡(jiǎn)要介紹, 是否起到幫助初步接觸 Router 時(shí)的同學(xué)們, 希望下面通過 Code 的方式能讓大家更好的理解和使用起來!
下面詳細(xì)介紹筆者封裝 JLRoutes 實(shí)現(xiàn)控制器跳轉(zhuǎn)的三個(gè)類:

JSDVCRouterConfig

這個(gè)文件主要用于管理所有 Router 映射到指定控制器類名(class), 以及相關(guān)參數(shù)的配置文件(title,needLogin等), 具體配置根據(jù)實(shí)際項(xiàng)目需求進(jìn)行即可;

  1. 為了編譯期能更好的檢查到錯(cuò)誤, 使用 extern NSString* const 聲明, 配合 NSString* const 實(shí)現(xiàn)指定 Router URL, 使用的時(shí)候直接通過外部聲明的常量字符串來指定跳轉(zhuǎn)即可;
  1. 這樣管理 Router URL 能更加方便閱讀和維護(hù), 如果直接使用 @"/login" 的方式來進(jìn)行綁定可讀性差, 很容易出現(xiàn)粗心大意導(dǎo)致的錯(cuò)誤;

代碼如下:

    //App 內(nèi)所有控制器
    extern NSString* const JSDVCRouteWebview;
    extern NSString* const JSDVCRouteLogin;
    @interface JSDVCRouterConfig : NSObject

    + (NSDictionary *)configMapInfo;
    
    @end
    
    //App 內(nèi)相關(guān)控制器
    NSString* const JSDVCRouteWebview = @"/webView";
    NSString* const JSDVCRouteLogin = @"/login";
    @implementation JSDVCRouterConfig
    
    + (NSDictionary *)configMapInfo {
    
    return @{
        JSDVCRouteWebview: @{@"class": @"JSDWebViewVC",
                             @"title": @"WebView",
                             @"flags": @"",
                             @"needLogin": @"",
        },
        JSDVCRouteLogin: @{@"class": @"JSDLoginVC",
                           @"title": @"登錄",
                           @"flags": @"",
                           @"needLogin": @"",
        },
        };
    @end 
JSDVCRouter

這個(gè)類內(nèi)部實(shí)現(xiàn)的事情非常簡(jiǎn)單, 繼承自 NSObject, 對(duì)外提供 注冊(cè)和調(diào)用 Router 接口, 在內(nèi)部調(diào)用 JLRoutes 提供的接口;

在項(xiàng)目中所有跳轉(zhuǎn)均使用此類提供的接口來調(diào)用 Router;
一個(gè)是默認(rèn)不帶任何參數(shù)
另一個(gè)可以攜帶我們需要的參數(shù)(NSDictionary);

[JSDVCRouter openURL:JSDVCRouteAppear]; //push 到 AppearVC; 
[JSDVCRouter openURL:JSDVCRouteAppear parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"name": @"jersey"}]; // Modal 到 Appear VC 并攜帶參數(shù) name;

單獨(dú)封裝一個(gè) JSDVCRouter 好處:

防止三方庫入侵. 其繼承自 NSObject 并不直接依賴于 JLRouter, 這樣在后期如果考慮更換三方庫, 或者自己封裝一套類似 JLRouter 提供的功能時(shí), 只需要對(duì)其修改即可, 其他地方均無需修改;
接口隔離保持統(tǒng)一, 可讀性更高;

@interface JSDVCRouter : NSObject

    + (BOOL)openURL:(NSString *)url;//調(diào)用 Router;
    + (BOOL)openURL:(NSString *)url parameters:(NSDictionary *)parameters;
    
    + (void)addRoute:(NSString* )route handler:(BOOL (^)(NSDictionary *parameters))handlerBlock;//注冊(cè) Router,調(diào)用 Router 時(shí)會(huì)觸發(fā)回調(diào); 

    @end
    #define JSDRouterURL(string) [NSURL URLWithString:string]

    @implementation JSDVCRouter
    
    + (BOOL)openURL:(NSString *)url {
        
        return [self routeURL:url parameters:nil];
    }
    
    + (BOOL)openURL:(NSString *)url parameters:(NSDictionary *)parameters {
        
       return [self routeURL:url parameters:parameters];
    }
    
    + (void)addRoute:(NSString *)route handler:(BOOL (^)(NSDictionary * _Nonnull parameters))handlerBlock {
        
        [JLRoutes addRoute:route handler:handlerBlock];
    }

    #pragma mark - mark JLRouter
    
    + (BOOL)routeURL:(NSString*)url parameters:(NSDictionary *)parameters{
        
        return [JLRoutes routeURL:JSDRouterURL(url) withParameters:parameters];
    }
    
    @end
    

JSDVCRouter+Handle

真正注冊(cè)和調(diào)用 Router 時(shí)處理回調(diào)控制器跳轉(zhuǎn)和參數(shù)賦值邏輯實(shí)現(xiàn)放在這里葫男。

注冊(cè) Router : 對(duì)控制器內(nèi)所有 Router 一一進(jìn)行注冊(cè)以及 TabBarIndex 切換和 處理返回 Router, 將回調(diào)統(tǒng)一轉(zhuǎn)發(fā)到定義的方法里頭。

處理 Router: 也就是注冊(cè)好 Router 之后, 調(diào)用相應(yīng) Router 時(shí), 我們?cè)谧?cè)時(shí)寫得回調(diào)方法, 這里是執(zhí)行控制器跳轉(zhuǎn)和傳參的邏輯崔列。

關(guān)于控制器跳轉(zhuǎn): 在觸發(fā) Router 時(shí), 我們能拿到 Router 映射到的 Map, 獲取到其 Class, 在通過 Class 來進(jìn)行初始初始化實(shí)例, 這里通過對(duì) UIViewController Category 找到當(dāng)前 visibleVC 來進(jìn)行 Push 或 Modal, 我們也可以根據(jù)業(yè)務(wù)方傳遞過來的參數(shù)來決定進(jìn)行 Push 或 Modal 以及是否需要執(zhí)行動(dòng)畫等等;

關(guān)于傳參: 傳遞過來的參數(shù)是字典的數(shù)據(jù)結(jié)構(gòu), 所以我們先檢測(cè)實(shí)例 VC 是否包含這個(gè)屬性, [vc respondsToSelector:NSSelectorFromString(key)], 如果 VC 有這個(gè)屬性則直接使用 KVC 的方式來進(jìn)行賦值, 為了防止在開發(fā)時(shí), 傳入的字典 Key 與 VC 屬性不匹配導(dǎo)致一些 Bug, 添加一層 NSAssert,這樣能在開發(fā)過程中更快找到問題梢褐!

筆者自行封裝的控制器跳轉(zhuǎn)邏輯可能有考慮不周的地方, 主要還得根據(jù)具體業(yè)務(wù)需求來做具體判斷;

下面分別是注冊(cè) Router 和匹配到 Router 之后回調(diào)處理代碼, 有點(diǎn)長(zhǎng)請(qǐng)耐心閱讀
Router 注冊(cè), 將三種類型回調(diào)處理統(tǒng)一

    @implementation JSDVCRouter (Handle)
    //注冊(cè) Router,  控制器的跳轉(zhuǎn) + UITabBarIndex 切換 + 頁面返回
    + (void)load {
        [self performSelectorOnMainThread:@selector(registerRouter) withObject:nil waitUntilDone:false];
    }
    + (void)registerRouter {
    
    //獲取全局 RouterMapInfo
    NSDictionary* routerMapInfo = [JSDVCRouterConfig configMapInfo];
    
    // router 對(duì)應(yīng)控制器路徑, 使用其來注冊(cè) Route, 當(dāng)調(diào)用當(dāng)前 Route 時(shí)會(huì)執(zhí)行回調(diào); 回調(diào)參數(shù) parameters: 在執(zhí)行 Route 時(shí)傳入的參數(shù);
    for (NSString* router in routerMapInfo.allKeys) {
        
        NSDictionary* routerMap = routerMapInfo[router];
        NSString* className = routerMap[kJSDVCRouteClassName];
        if (JSDIsString(className)) {
            /*注冊(cè)所有控制器 Router, 使用 [JSDVCRouter openURL:JSDVCRouteAppear]; push 到 AppearVC;
            [JSDVCRouter openURL:JSDVCRouteAppear parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"name": @"jersey"}];  Modal 到 Appear VC 并攜帶參數(shù) name;
             */
            [self addRoute:router handler:^BOOL(NSDictionary * _Nonnull parameters) {
                //執(zhí)行路由匹配成功之后,跳轉(zhuǎn)邏輯回調(diào);
                /*執(zhí)行 Route 回調(diào); 處理控制器跳轉(zhuǎn) + 傳參;
                ** routerMap: 當(dāng)前 route 映射的  routeMap; 我們?cè)?RouterConfig 配置的 Map;
                ** parameters: 調(diào)用 route 時(shí), 傳入的參數(shù);
                 */
                return [self executeRouterClassName:className routerMap:routerMap parameters:parameters];
            }];
        }
    }
    
    // 注冊(cè) Router 到指定TabBar Index; 使用 [JSDVCRouter openURL:JSDVCRouteCafeTab] 切換到 Cafe Index
    [self addRoute:@"/rootTab/:index" handler:^BOOL(NSDictionary * _Nonnull parameters) {
        NSInteger index = [parameters[@"index"] integerValue];
        // 處理 UITabBarControllerIndex 切換;
        UITabBarController* tabBarVC = (UITabBarController* )[UIViewController jsd_rootViewController];
        if ([tabBarVC isKindOfClass:[UITabBarController class]] && index >= 0 && tabBarVC.viewControllers.count >= index) {
            UIViewController* indexVC = tabBarVC.viewControllers[index];
            if ([indexVC isKindOfClass:[UINavigationController class]]) {
                indexVC = ((UINavigationController *)indexVC).topViewController;
            }
            //傳參
            [self setupParameters:parameters forViewController:indexVC];
            tabBarVC.selectedIndex = index;
            return YES;
        } else {
            return NO;
        }
    }];
    // 注冊(cè)返回上層頁面 Router, 使用 [JSDVCRouter openURL:kJSDVCRouteSegueBack] 返回上一頁 或 [JSDVCRouter openURL:kJSDVCRouteSegueBack parameters:@{kJSDVCRouteBackIndex: @(2)}]  返回前兩頁
    [self addRoute:kJSDVCRouteSegueBack handler:^BOOL(NSDictionary * _Nonnull parameters) {
        
        return [self executeBackRouterParameters:parameters];
    }];
    }

Router 匹配到之后回調(diào): 實(shí)例化控制器, 參數(shù)賦值, 頁面跳轉(zhuǎn)

#pragma mark - execute Router VC    
// 當(dāng)查找到指定 Router 時(shí), 觸發(fā)路由回調(diào)邏輯; 找不到已注冊(cè) Router 則直接返回 NO; 如需要的話, 也可以在這里注冊(cè)一個(gè)全局未匹配到 Router 執(zhí)行的回調(diào)進(jìn)行異常處理;
+ (BOOL)executeRouterClassName:(NSString *)className routerMap:(NSDictionary* )routerMap parameters:(NSDictionary* )parameters {
    // 攔截 Router 映射參數(shù),是否需要登錄才可跳轉(zhuǎn);
    BOOL needLogin = [routerMap[kJSDVCRouteClassNeedLogin] boolValue];
    if (needLogin && !userIsLogin) {
        [JSDVCRouter openURL:JSDVCRouteLogin];
        return NO;
    }
    //統(tǒng)一初始化控制器,傳參和跳轉(zhuǎn);
    UIViewController* vc = [self viewControllerWithClassName:className routerMap:routerMap parameters: parameters];
    if (vc) {
        [self gotoViewController:vc parameters:parameters];
        return YES;
    } else {
        return NO;
    }
}
// 根據(jù) Router 映射到的類名實(shí)例化控制器;
+ (UIViewController *)viewControllerWithClassName:(NSString *)className routerMap:(NSDictionary *)routerMap parameters:(NSDictionary* )parameters {
    
    id vc = [[NSClassFromString(className) alloc] init];
    if (![vc isKindOfClass:[UIViewController class]]) {
        vc = nil;
    }
#if DEBUG
    //vc不是UIViewController
    NSAssert(vc, @"%s: %@ is not kind of UIViewController class, routerMap: %@",__func__ ,className, routerMap);
#endif
    //參數(shù)賦值
    [self setupParameters:parameters forViewController:vc];
    
    return vc;
}
// 對(duì) VC 參數(shù)賦值
+ (void)setupParameters:(NSDictionary *)params forViewController:(UIViewController* )vc {
    
    for (NSString *key in params.allKeys) {
        BOOL hasKey = [vc respondsToSelector:NSSelectorFromString(key)];
        BOOL notNil = params[key] != nil;
        if (hasKey && notNil) {
            [vc setValue:params[key] forKey:key];
        }
        
#if DEBUG
    //vc沒有相應(yīng)屬性旺遮,但卻傳了值
        if ([key hasPrefix:@"JLRoute"]==NO &&
            [key hasPrefix:@"JSDVCRoute"]==NO && [params[@"JLRoutePattern"] rangeOfString:[NSString stringWithFormat:@":%@",key]].location==NSNotFound) {
            NSAssert(hasKey == YES, @"%s: %@ is not property for the key %@",__func__ ,vc,key);
        }
#endif
    };
}
// 跳轉(zhuǎn)和參數(shù)設(shè)置;
+ (void)gotoViewController:(UIViewController *)vc parameters:(NSDictionary *)parameters {
    
    UIViewController* currentVC = [UIViewController jsd_findVisibleViewController];
    NSString *segue = parameters[kJSDVCRouteSegue] ? parameters[kJSDVCRouteSegue] : kJSDVCRouteSeguePush; //  決定 present 或者 Push; 默認(rèn)值 Push
    BOOL animated = parameters[kJSDVCRouteAnimated] ? [parameters[kJSDVCRouteAnimated] boolValue] : YES;  // 轉(zhuǎn)場(chǎng)動(dòng)畫;
    NSLog(@"%s 跳轉(zhuǎn): %@ %@ %@",__func__ ,currentVC, segue,vc);
    
    if ([segue isEqualToString:kJSDVCRouteSeguePush]) { //PUSH
        if (currentVC.navigationController) {
            NSString *backIndexString = [NSString stringWithFormat:@"%@",parameters[kJSDVCRouteBackIndex]];
            UINavigationController* nav = currentVC.navigationController;
            if ([backIndexString isEqualToString:kJSDVCRouteIndexRoot]) {
                NSMutableArray *vcs = [NSMutableArray arrayWithObject:nav.viewControllers.firstObject];
                [vcs addObject:vc];
                [nav setViewControllers:vcs animated:animated];
                
            } else if ([backIndexString integerValue] && [backIndexString integerValue] < nav.viewControllers.count) {
                //移除掉指定數(shù)量的 VC, 在Push;
                NSMutableArray *vcs = [nav.viewControllers mutableCopy];
                [vcs removeObjectsInRange:NSMakeRange(vcs.count - [backIndexString integerValue], [backIndexString integerValue])];
                nav.viewControllers = vcs;
                [nav pushViewController:vc animated:YES];
            } else {
                [nav pushViewController:vc animated:animated];
            }
        }
        else { //由于無導(dǎo)航欄, 直接執(zhí)行 Modal
            BOOL needNavigation = parameters[kJSDVCRouteSegueNeedNavigation] ? NO : YES;
            if (needNavigation) {
                UINavigationController* navigationVC = [[UINavigationController alloc] initWithRootViewController:vc];
                //vc.modalPresentationStyle = UIModalPresentationFullScreen;
                [currentVC presentViewController:navigationVC animated:YES completion:nil];
            }
            else {
                //vc.modalPresentationStyle = UIModalPresentationFullScreen;
                [currentVC presentViewController:vc animated:animated completion:nil];
            }
        }
    }
    else { //Modal
        BOOL needNavigation = parameters[kJSDVCRouteSegueNeedNavigation] ? parameters[kJSDVCRouteSegueNeedNavigation] : NO;
        if (needNavigation) {
            UINavigationController* navigationVC = [[UINavigationController alloc] initWithRootViewController:vc];
            //vc.modalPresentationStyle = UIModalPresentationFullScreen;
            [currentVC presentViewController:navigationVC animated:animated completion:nil];
        }
        else {
            //vc.modalPresentationStyle = UIModalPresentationFullScreen;
            [currentVC presentViewController:vc animated:animated completion:nil];
        }
    }
}

能堅(jiān)持看到這里, 應(yīng)該對(duì) Router 進(jìn)行控制器跳轉(zhuǎn)已經(jīng)有了個(gè)不錯(cuò)的理解!

待補(bǔ)充

App 內(nèi)部跳轉(zhuǎn)除了, 頻繁的控制器之間切換外, 還有比如跳轉(zhuǎn)到 H5, 或者跳轉(zhuǎn)到 WebView 等;
App 外跳轉(zhuǎn)則包含 Scheme 啟動(dòng), 3D Touch, UniversalLink, 點(diǎn)擊通知等都會(huì)觸發(fā);
這些包含跳轉(zhuǎn), 頁面切換的我們均可以統(tǒng)一使用 Router 來進(jìn)行有效的管理, 使 App 變得更加動(dòng)態(tài)化, 模塊之間耦合度更低;

  • 支持 H5 跳轉(zhuǎn)
  • 外部 Scheme 啟動(dòng) App
  • UniversalLink
  • 3D Touch Shortcut
  • 支持后臺(tái)動(dòng)態(tài)下發(fā) RouterMap 配置

最后

希望此篇文章對(duì)您有所幫助盈咳,如有不對(duì)的地方耿眉,希望大家能留言指出糾正。
emmmmm,每次看到這么長(zhǎng)一串代碼, 要么是直接跳過, 要么就是認(rèn)認(rèn)真真看完之后馬上啟動(dòng) Xcode Coding 一遍, Commond + R 實(shí)踐一遍, 為了方便大家理解獻(xiàn)上
Demo 如果覺得對(duì)你有幫助, 麻煩大家給個(gè) Star 謝謝??S阆臁C簟!U苫筐骇!
學(xué)習(xí)的路上,與君共勉!!!

本文原創(chuàng)作者:Jersey. 歡迎轉(zhuǎn)載,請(qǐng)注明出處和本文鏈接

參考-鏈接

iOS 組件化 —— 路由設(shè)計(jì)思路分析
iOS架構(gòu)實(shí)踐干貨
iOS應(yīng)用架構(gòu)談 組件化方案
蘑菇街 App 的組件化之路
實(shí)戰(zhàn)Demo 如果覺得對(duì)你有幫助, 麻煩大家給個(gè) Star 謝謝??=酢S倒印!Q滥饺鹃!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市间雀,隨后出現(xiàn)的幾起案子悔详,更是在濱河造成了極大的恐慌,老刑警劉巖惹挟,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茄螃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡连锯,警方通過查閱死者的電腦和手機(jī)归苍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來运怖,“玉大人拼弃,你說我怎么就攤上這事∫≌梗” “怎么了吻氧?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)咏连。 經(jīng)常有香客問我盯孙,道長(zhǎng),這世上最難降的妖魔是什么祟滴? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任振惰,我火速辦了婚禮,結(jié)果婚禮上垄懂,老公的妹妹穿的比我還像新娘骑晶。我一直安慰自己痛垛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布透罢。 她就那樣靜靜地躺著榜晦,像睡著了一般冠蒋。 火紅的嫁衣襯著肌膚如雪羽圃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天抖剿,我揣著相機(jī)與錄音朽寞,去河邊找鬼。 笑死斩郎,一個(gè)胖子當(dāng)著我的面吹牛脑融,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缩宜,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼肘迎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了锻煌?” 一聲冷哼從身側(cè)響起妓布,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宋梧,沒想到半個(gè)月后匣沼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捂龄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年释涛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倦沧。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唇撬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出展融,到底是詐尸還是另有隱情局荚,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布愈污,位于F島的核電站耀态,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏暂雹。R本人自食惡果不足惜首装,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杭跪。 院中可真熱鬧仙逻,春花似錦驰吓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缺亮,卻和暖如春翁涤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萌踱。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工葵礼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人并鸵。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓鸳粉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親园担。 傳聞我的和親對(duì)象是個(gè)殘疾皇子届谈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353