前言
組件化, 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;
- 路由是什么,解決了什么問題
上面一幅圖很形象的展示了項(xiàng)目中各個(gè)控制器模塊之間錯(cuò)綜復(fù)雜的關(guān)系, 當(dāng)我們?cè)谔幚聿划?dāng)?shù)那闆r下可能更加糟糕.
使用 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)不大的話, 幾乎一勞永逸拘悦;
- 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", },
};
- 封裝 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ù)賦值代碼; - 根據(jù)約定 Path 進(jìn)行跳轉(zhuǎn): 上面 1 2 都準(zhǔn)備好之后, 即可輕松的進(jìn)行控制器跳轉(zhuǎn) [JSDVCRouter openURL:BVC];
業(yè)務(wù)變更后期維護(hù)
- Map 維護(hù): 隨著業(yè)務(wù)發(fā)展, 當(dāng)有新的頁面加入時(shí), 對(duì) Map 添加一個(gè)指定的 Path 和綁定的相應(yīng)參數(shù);
- 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)行即可;
- 為了編譯期能更好的檢查到錯(cuò)誤, 使用 extern NSString* const 聲明, 配合 NSString* const 實(shí)現(xiàn)指定 Router URL, 使用的時(shí)候直接通過外部聲明的常量字符串來指定跳轉(zhuǎn)即可;
- 這樣管理 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í)的路上,與君共勉!!!
參考-鏈接
iOS 組件化 —— 路由設(shè)計(jì)思路分析
iOS架構(gòu)實(shí)踐干貨
iOS應(yīng)用架構(gòu)談 組件化方案
蘑菇街 App 的組件化之路
實(shí)戰(zhàn)Demo 如果覺得對(duì)你有幫助, 麻煩大家給個(gè) Star 謝謝??=酢S倒印!Q滥饺鹃!