一、簡介
JLRoutes 是一個(gè)帶有簡單的基于塊的API的URL路由庫史翘。它旨在使您在應(yīng)用程序中以最少的代碼處理復(fù)雜的URL方案變得非常簡單。
JLRoutes 可以很方便的處理不同 URL schemes 以及解析它們的參數(shù),并通過回調(diào) block 來處理 URL 對應(yīng)的操作 , 是一個(gè)可以用于處理復(fù)雜跳轉(zhuǎn)邏輯的三方庫耻蛇。
二、使用場景
1胞此、在日常開發(fā)中臣咖,push、present 出現(xiàn)在整個(gè)程序的各個(gè)地方漱牵,如果你想快速理清一個(gè)項(xiàng)目的整體邏輯夺蛇,那會非常麻煩。大多數(shù)情況 , 你得找到代碼目錄酣胀,根據(jù)層級結(jié)構(gòu)分出關(guān)系刁赦,然后找到對應(yīng)的push位置,尋找下一級頁面闻镶,如果本身項(xiàng)目的目錄就非常亂甚脉,那么如果要了解一個(gè)項(xiàng)目的整體跳轉(zhuǎn)邏輯,將會更加困難铆农。
如果能把整個(gè)項(xiàng)目的跳轉(zhuǎn)邏輯都給抽取出來牺氨,單獨(dú)放在一個(gè)類,模塊化管理,那么思路就會清晰很多猴凹,甚至可以用 XMind 根據(jù)代碼畫出整個(gè)項(xiàng)目的樹狀圖夷狰。2、如果所處公司存在多個(gè)app郊霎,app之間互相推薦互相跳轉(zhuǎn)是再正常不過的需求沼头,就類似于QQ、微信书劝、第三方分享跳轉(zhuǎn)等进倍。如果用Appdelegate 原生方法進(jìn)行攔截,所做的事至少得是判斷 Scheme 是否匹配购对,想辦法進(jìn)入需要跳到的界面背捌,如果要涉及傳參,就更加麻煩洞斯。
3毡庆、如果用戶是從 PC端 識別二維碼,或者通過鏈接想要進(jìn)入 app 指定頁面烙如。
三么抗、原理
JLRoutes 本質(zhì)可以理解為:保存一個(gè)全局的字典,key 是 URL亚铁,value 是對應(yīng)存放 block 的數(shù)組蝇刀,URL 和 block 都會常駐在內(nèi)存中,當(dāng)打開一個(gè) URL 時(shí)徘溢,JLRoutes 就可以遍歷這個(gè)全局的字典吞琐,通過 URL 來執(zhí)行對應(yīng)的 block。下面是根據(jù)自己的理解畫的層次圖:
具體理解:
1然爆、
routeControllersMap
是全局的單例可變字典2站粟、這個(gè)字典的 key 值對應(yīng)一個(gè)標(biāo)識,源碼中稱之為 scheme曾雕,為了不混淆奴烙,咱們就叫其為 JLRoutes 對象標(biāo)識。這個(gè)標(biāo)識對應(yīng)的value 值為
routesController
(JLRoutes類的對象:JLRoutes *routesController)剖张。-
3切诀、JLRoutes的對象(routesController)有很多屬性,常用的有兩個(gè)屬性:
-
NSString *scheme
:也就是上面所說的 JLRoutes對象標(biāo)識搔弄,也就是說幅虑,此 value 值記錄了自己的 key 值。 -
NSMutableArray *routes
:此數(shù)組中存放了JLRRouteDefinition
對象顾犹。
-
4倒庵、
JLRRouteDefinition
對象為最終的具體模型褒墨,也就是說你注冊的跳轉(zhuǎn)邏輯的所有信息,都存在于這個(gè)模型中哄芜,包括要實(shí)施操作的handlerBlock
(執(zhí)行操作的block代碼塊)貌亭、scheme
(JLRoutes對象標(biāo)識)柬唯、pattern
(模式)认臊、priority
(優(yōu)先級)。
四锄奢、開發(fā)步驟
在正式進(jìn)行 JLRoutes 開發(fā)之前失晴,了解如何通過設(shè)置app的 URL Scheme由外部跳轉(zhuǎn)到app?具體步驟我已在之前的一篇文章中詳細(xì)說明了(如何自定義 URL Scheme 進(jìn)行跳轉(zhuǎn))拘央,此處略過涂屁,不明白的可以先去大致了解下原理。
1灰伟、配置 URL Schemes
一個(gè) app 可以對應(yīng)多個(gè) URL Schemes拆又,如下圖 info.plist
配置,在 Safari
中栏账,只要輸入 JLRouteSchemeOne://
或 JLRouteSchemeTwo://
都可以打開該 app帖族,而 URL identifier
最好是保證其唯一性,這里咱們?yōu)閍pp設(shè)置了2個(gè) URL Schemes
挡爵,是為了后面手動解析URL而做的準(zhǔn)備竖般。
2、注冊 JLRoutes
首先 , 考慮的問題有兩個(gè)茶鹃,一是什么時(shí)候注冊路由涣雕,二是在什么地方注冊路由。
比如一個(gè)項(xiàng)目的 tabbarItem
有2個(gè)闭翩,那么這2個(gè)模塊的跳轉(zhuǎn)挣郭,并不是由一個(gè) navigationController
來完成,所以考慮到這點(diǎn)疗韵,我們可以創(chuàng)建一個(gè)分類丈屹,將跳轉(zhuǎn)邏輯放在其中,在初始化 tabbarController
時(shí)進(jìn)行注冊路由跳轉(zhuǎn)邏輯伶棒。
注冊路由的方式有很多種:
1旺垒、全局JLRoutes注冊
[[JLRoutes globalRoutes] addRoute:@"取url內(nèi)容值的標(biāo)識" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
return YES; // 一旦匹配,立即返回 YES
}];
此方法對應(yīng)的JLRoutes對象標(biāo)識為 JLRoutesGlobalRoutesScheme
肤无,由下述源碼可知 , 用 globalRoutes
方式創(chuàng)建的JLRoutes對象先蒋,無論創(chuàng)建多少次,始終對應(yīng)著同一個(gè)實(shí)例宛渐。也就是說竞漾,無論你調(diào)用上述方法多少次眯搭,盡管 @”取url內(nèi)容值的標(biāo)識”
和 block塊內(nèi)容不一樣,最后都會執(zhí)行第一次注冊的內(nèi)容业岁。此方法和咱們要實(shí)現(xiàn)的2個(gè) tabbarItem
對應(yīng)2種跳轉(zhuǎn)要求不合鳞仙,因?yàn)樵蹅円蟮氖?block塊中的 navigationController
為2個(gè)不同的實(shí)例對象。
2笔时、自定義命名空間注冊
[[JLRoutes routesForScheme:@"第一模塊的標(biāo)識"] addRoute:@"取url內(nèi)容值的標(biāo)識" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
return YES; // 一旦匹配棍好,立即返回 YES
}];
此注冊方法所得的JLRoutes對象都是唯一的,而這才是咱們真正需要的允耿。
3借笙、定義優(yōu)先級注冊
[[JLRoutes globalRoutes] addRoute:@"取url內(nèi)容值的標(biāo)識" priority:1 handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
return YES; // 一旦匹配,立即返回 YES
}];
簡單來說:如果不設(shè)置優(yōu)先級较锡,所有的注冊優(yōu)先級都為 0业稼。
當(dāng)標(biāo)識了優(yōu)先級進(jìn)行注冊后,JLRRouteDefinition 對象(最終模型)在 JLRoutes對象的 routes數(shù)組 中將進(jìn)行排序蚂蕴,類似于選擇排序低散,當(dāng)通過route對象尋找到其 routes數(shù)組 后,將會遍歷整個(gè) routes數(shù)組骡楼,優(yōu)先級高的 JLRRouteDefinition對象 將會被最先匹配熔号,然后return YES
,并停止遍歷君编。
咱們暫時(shí)用不上這個(gè)優(yōu)先級跨嘉,就不進(jìn)行過多講述,因?yàn)樵蹅冏缘?個(gè)跳轉(zhuǎn)吃嘿,每個(gè)對應(yīng)的routes數(shù)組中元素僅為1個(gè)祠乃。
4、定義多個(gè) "取url內(nèi)容值的標(biāo)識" 進(jìn)行注冊
[[JLRoutes globalRoutes] addRoutes:@[@"取url內(nèi)容值的標(biāo)識", @"取url內(nèi)容值的標(biāo)識"] handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
return YES; // 一旦匹配兑燥,立即返回 YES
}];
我們使用第二種方法進(jìn)行注冊:取url內(nèi)容值的標(biāo)識
暫時(shí)為 nil
[[JLRoutes routesForScheme:@"JLRouteSchemeOne"] addRoute:nil handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
return YES;
}];
[[JLRoutes routesForScheme:@"JLRouteSchemeTwo"] addRoute:nil handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
return YES;
}];
3亮瓷、點(diǎn)擊跳轉(zhuǎn)
在開始這兩個(gè) URL scheme 都添加進(jìn)了 info.plist
,并用第二種注冊方法(自定義命名空間注冊)進(jìn)行注冊降瞳,接下來這兩個(gè)方法就可以進(jìn)行跳轉(zhuǎn):
- (void)clickBtn {
NSString *customURL = @"JLRouteSchemeOne://OneDetailViewController";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]];
}
- (void)clickBtn {
NSString *customURL = @"JLRouteSchemeTwo://TwoDetailViewController";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]];
}
4嘱支、實(shí)現(xiàn) openURL 方法
openURL方法在此處對所有的跳轉(zhuǎn)進(jìn)行攔截,手動解析處理挣饥,再交于JLRoutes除师。
手動解析URL:如果Scheme從網(wǎng)頁跳轉(zhuǎn)過來,攔截到的會變成小寫扔枫,所以直接將 URL Scheme 攔截下來汛聚,轉(zhuǎn)換小寫進(jìn)行判斷。經(jīng)過處理之后短荐,交于JLRoutes進(jìn)行解析倚舀,尋找具體的操作叹哭。
這里也解釋了為什么在 info.plist
文件中,要設(shè)定2個(gè)不同的URL Scheme痕貌。當(dāng)在2個(gè)不同模塊中進(jìn)行跳轉(zhuǎn)點(diǎn)擊時(shí)风罩,這個(gè)方法是必經(jīng)的,進(jìn)行攔截舵稠,判斷具體是哪個(gè)模塊后交于JLRoutes解析超升。
// iOS 9.0前方法
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
NSLog(@"Calling Application Bundle ID: %@", sourceApplication);
NSLog(@"URL scheme: %@", [url scheme]);
NSLog(@"URL query: %@", [url query]);
// 從瀏覽器打開時(shí)候會自動全部轉(zhuǎn)成小寫,而從應(yīng)用內(nèi)調(diào)用的話大小寫不會變化
// 為了方便判斷所以統(tǒng)一轉(zhuǎn)成小寫來判斷
NSString *urlSchemeStr = [[url scheme] lowercaseString]; // url scheme 轉(zhuǎn)換為小寫的字符串
NSLog(@"urlSchemeStr: %@",urlSchemeStr);
if ([urlSchemeStr isEqualToString:@"jlrouteschemeone"]) {
// 要和 info.plist 的 URL types 里面的一致
return [[JLRoutes routesForScheme:@"JLRouteSchemeOne"]routeURL:url];
} else if ([urlSchemeStr isEqualToString:@"jlrouteschemetwo"]) {
// 要和 info.plist 的 URL types 里面的一致
return [[JLRoutes routesForScheme:@"JLRouteSchemeTwo"]routeURL:url];
}
return YES;
}
// iOS 9.0后方法
- (BOOL)application:(UIApplication *)app openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSLog(@"options: %@", options);
NSLog(@"Calling Application Bundle ID: %@", [options objectForKey:@"UIApplicationOpenURLOptionsSourceApplicationKey"]);
NSLog(@"URL scheme: %@", [url scheme]);
NSLog(@"URL host : %@", [url host]);
NSLog(@"URL query: %@", [url query]);
// 從瀏覽器打開時(shí)候會自動全部轉(zhuǎn)成小寫柱查,而從應(yīng)用內(nèi)調(diào)用的話大小寫不會變化
// 為了方便判斷所以統(tǒng)一轉(zhuǎn)成小寫來判斷
NSString *urlSchemeStr = [[url scheme] lowercaseString]; // url scheme 轉(zhuǎn)換為小寫的字符串
NSLog(@"urlSchemeStr: %@",urlSchemeStr);
if ([urlSchemeStr isEqualToString:@"jlrouteschemeone"]) {
// 要和 info.plist 的 URL types 里面的一致
return [[JLRoutes routesForScheme:@"JLRouteSchemeOne"]routeURL:url];
} else if ([urlSchemeStr isEqualToString:@"jlrouteschemetwo"]) {
// 要和 info.plist 的 URL types 里面的一致
return [[JLRoutes routesForScheme:@"JLRouteSchemeTwo"]routeURL:url];
}
return YES;
}
5廓俭、參數(shù)傳遞云石,以及 tabbarController 選中問題處理
參數(shù)傳遞需要進(jìn)行一一對應(yīng)
[[JLRoutes routesForScheme:@"JLRouteSchemeTwo"]addRoute:@"/:ViewController/:userID/:pass" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
NSLog(@"parameters: %@",parameters);
NSLog(@"userID: %@",parameters[@"userID"]);
NSLog(@"pass: %@",parameters[@"pass"]);
NSLog(@"-----第二模塊-----");
Class class = NSClassFromString(parameters[@"ViewController"]);
[navVc pushViewController:[[class alloc]init] animated:YES];
self.selectedIndex = 1; // 解決從app外跳轉(zhuǎn)進(jìn)來的 tabbar 選中問題
return YES;
}];
點(diǎn)擊方法如下:
- (void)clickBtn {
NSString *customURL = @"JLRouteSchemeTwo://TwoDetailViewController/我是userID/我是pwd";
// 中文傳輸需要進(jìn)行轉(zhuǎn)義
customURL = [customURL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]];
}
處理從網(wǎng)頁等跳轉(zhuǎn)過來唉工,比如直接跳到第二模塊的第二級控制器,實(shí)際上已經(jīng)跳轉(zhuǎn)了汹忠,tabbarItem還是選中的第一個(gè)淋硝。只需要在block塊中處理一下 selectedIndex
就行。
參考鏈接:JLRoutes路由跳轉(zhuǎn)