iOS使用核心的50行代碼實(shí)現(xiàn)一個(gè)路由組件

使用組件化是為了解耦處理赴背,多個(gè)模塊之間通過(guò)協(xié)議進(jìn)行交互。而負(fù)責(zé)解析協(xié)議笔咽,找到目的控制器桦他,或者是返回對(duì)象給調(diào)用者的這個(gè)組件就是路由組件蔫巩。本文講解如何使用核心的50行代碼實(shí)現(xiàn)一個(gè)路由組件。

  • 組件化和路由
  • 路由的實(shí)現(xiàn)
    • 路由注冊(cè)實(shí)現(xiàn)
    • 路由使用實(shí)現(xiàn)
  • 客戶端的使用
  • 一些小想法

組件化和路由

之前看過(guò)挺多的關(guān)于路由管理快压、路由處理的文章批幌,常常會(huì)和組件化出現(xiàn)在一起,一開(kāi)始不知道為何路由和組件化出現(xiàn)在一起嗓节,后來(lái)公司的項(xiàng)目中使用了路由組件(他本身也是一個(gè)組件,確切的說(shuō)是一個(gè)中間人或者中介者)皆警,才突然想明白了拦宣,原來(lái)如此。
使用組件化是為了解耦處理信姓,多個(gè)模塊之間通過(guò)協(xié)議進(jìn)行交互鸵隧。而負(fù)責(zé)解析協(xié)議,找到目的控制器意推,或者是返回對(duì)象給調(diào)用者的這個(gè)組件就是路由組件豆瘫。

路由組件的職責(zé)主要是:

  • 給注冊(cè)者提供注冊(cè)接口
    • 注冊(cè)者傳遞path和path對(duì)應(yīng)的block,block的具體實(shí)現(xiàn)又注冊(cè)者自己處理
  • 給調(diào)用者提供使用接口
    • 調(diào)用者最簡(jiǎn)單可以傳遞一個(gè)path給路由組件發(fā)起調(diào)用菊值,路由組件會(huì)把具體的處理轉(zhuǎn)發(fā)給注冊(cè)者外驱,理論上是可以任意的操作育灸,包括頁(yè)面跳轉(zhuǎn)、彈窗提示昵宇、返回一個(gè)值給調(diào)用者等

下面會(huì)會(huì)在以上分析的基礎(chǔ)上實(shí)現(xiàn)一個(gè)簡(jiǎn)單的路由組件磅崭,對(duì)應(yīng)的代碼可以在YTRouterDemo這里找到

路由的實(shí)現(xiàn)

路由的實(shí)現(xiàn)包括兩部分:路由注冊(cè)實(shí)現(xiàn)以及路由使用實(shí)現(xiàn)

路由注冊(cè)實(shí)現(xiàn)

路由注冊(cè)實(shí)現(xiàn)時(shí)序圖:

路由注冊(cè)實(shí)現(xiàn)時(shí)序圖

如上圖所示,步驟很簡(jiǎn)單:

  • 初始化一個(gè)YTRouterActionObject對(duì)象瓦哎,用于保存path和對(duì)應(yīng)的blok
  • 獲取到路徑對(duì)應(yīng)的節(jié)點(diǎn)砸喻,path會(huì)使用"/"符拆分為多個(gè)pathItem,每個(gè)pathItem都會(huì)保存在一個(gè)Dictionary對(duì)應(yīng)的位置上蒋譬,subRouterMapWithPath負(fù)責(zé)深度遍歷Dictionary割岛,然后找到對(duì)應(yīng)的位置
  • YTRouterActionObject對(duì)象保存在上一步找到的位置中

以上步驟對(duì)應(yīng)的代碼如下:

- (void)registerPath:(NSString *)path actionBlock:(RouterActionBlock)actionBlock {
    YTRouterActionObject *actionObject = [YTRouterActionObject new];
    actionObject.path = path;
    actionObject.actionBlock = actionBlock;
    NSMutableDictionary *subRouter = [self subRouterMapWithPath:path];
    subRouter[YTRouterActionObjectKey] = actionObject;
}

- (NSMutableDictionary *)subRouterMapWithPath:(NSString *)path {
    NSArray *components = [path componentsSeparatedByString:@"/"];
    NSMutableDictionary *subRouter = self.routerMap;
    for (NSString *component in components) {
        if (component.length == 0) {
            continue;
        }
        if (!subRouter[component]) {
            subRouter[component] = [NSMutableDictionary new];
        }
        subRouter = subRouter[component];
    }
    return subRouter;
}

在Demo中注冊(cè)的幾個(gè)路由最終的配置如下,比如home/messagelist對(duì)應(yīng)的路由配置保存在<YTRouterActionObject: 0x6040000365e0>對(duì)象中

Printing description of self->_routerMap:
{
    home =     {
        "_" = "<YTRouterActionObject: 0x60c00003b040>";
        messagelist =         {
            "_" = "<YTRouterActionObject: 0x6040000365e0>";
            detail =             {
                "_" = "<YTRouterActionObject: 0x600000038ec0>";
            };
            getmessage =             {
                "_" = "<YTRouterActionObject: 0x600000038e80>";
            };
        };
    };
}

路由使用實(shí)現(xiàn)

路由使用實(shí)現(xiàn)時(shí)序圖:

路由使用實(shí)現(xiàn)時(shí)序圖

如上圖所示犯助,步驟很簡(jiǎn)單:

  • 從注冊(cè)的配置中找到匹配的YTRouterActionObject對(duì)象
  • 執(zhí)行YTRouterActionObject對(duì)象的actionBlock癣漆,會(huì)傳遞一個(gè)YTRouterActionCallbackObject對(duì)象,如果調(diào)用者需要的是返回值也切,可以使用YTRouterActionCallbackObject對(duì)象的actionCallbackBlock傳遞一個(gè)返回值扑媚,這個(gè)actionBlock是又業(yè)務(wù)方的注冊(cè)者實(shí)現(xiàn)的

以上步驟對(duì)應(yīng)的代碼如下:

- (BOOL)runWithActionCallbackObject:(YTRouterActionCallbackObject *)actionCallbackObject {
    // 判斷是否支持scheme
    if (![self canAcceptScheme:actionCallbackObject.uri.scheme]) {
        return NO;
    }
    // 獲取path對(duì)應(yīng)的ActionObject
    YTRouterActionObject *actionObject = [self actionObjectWithPath:actionCallbackObject.uri.path];
    // 執(zhí)行Path注冊(cè)的對(duì)應(yīng)Block
    !actionObject.actionBlock ?: actionObject.actionBlock(actionCallbackObject);
    return YES;
}

- (YTRouterActionObject *)actionObjectWithPath:(NSString *)path {
    NSMutableDictionary *subRouter = [self subRouterMapWithPath:path];
    return subRouter[YTRouterActionObjectKey];
}

客戶端的使用

以上講到了核心的路由注冊(cè)實(shí)現(xiàn)路由使用實(shí)現(xiàn),總共代碼還沒(méi)有50行雷恃,所以還是很簡(jiǎn)單的疆股,接下來(lái)會(huì)講下客戶端的使用步驟,包括

  • 客戶端注冊(cè)者注冊(cè)
  • 客戶端調(diào)用者使用

客戶端注冊(cè)者注冊(cè)

注冊(cè)的時(shí)機(jī)需要比較找倒槐,考慮到集成的方便旬痹,選擇在load方法中處理路由注冊(cè),如下代碼所示讨越,添加了幾個(gè)測(cè)試的路由两残,分兩種情況來(lái)說(shuō)明下使用

1、不需要返回值
如下注冊(cè)"home/messagelist"的是一個(gè)頁(yè)面跳轉(zhuǎn)的路由把跨,actionBlock的參數(shù)是一個(gè)YTRouterActionCallbackObject對(duì)象人弓,可以從YTRouterActionCallbackObject對(duì)象或者到參數(shù),關(guān)于如何傳遞值着逐,會(huì)在下面的客戶端調(diào)用者使用這里講到崔赌。然后在actionBlock處理目的頁(yè)面的初始化、參數(shù)設(shè)置等步驟耸别,然后執(zhí)行頁(yè)面跳轉(zhuǎn)健芭。

2、需要返回值
如下注冊(cè)"home/messagelist/getmessage"的是一個(gè)提供返回值的路由秀姐,同樣也可以從YTRouterActionCallbackObject對(duì)象獲取參數(shù)慈迈,另外YTRouterActionCallbackObject對(duì)象還有一個(gè)actionCallbackBlock屬性是專(zhuān)門(mén)處理返回參數(shù)給調(diào)用者的,如下的代碼只是簡(jiǎn)單返回一個(gè)字符串省有,在更加具體的業(yè)務(wù)場(chǎng)景中痒留,這里會(huì)設(shè)置接口調(diào)用谴麦、數(shù)據(jù)庫(kù)查詢等任務(wù),最后把結(jié)果返回狭瞎。

@implementation ModuleAUriRegister

+ (void)load {
    [[YTRouterManager sharedRouterManager] registerPath:@"home/messagelist" actionBlock:^(YTRouterActionCallbackObject *callbackObject) {
        MessageListViewController *messageListVC = [MessageListViewController new];
        NSString *title = callbackObject.uri.params[@"title"];
        messageListVC.title = title;
        [[UIViewController yt_currentViewControlloer].navigationController pushViewController:messageListVC animated:YES];;
    }];
    [[YTRouterManager sharedRouterManager] registerPath:@"home/" actionBlock:^(YTRouterActionCallbackObject *callbackObject) {
        
    }];
    [[YTRouterManager sharedRouterManager] registerPath:@"home/messagelist/detail" actionBlock:^(YTRouterActionCallbackObject *callbackObject) {
        
    }];
    [[YTRouterManager sharedRouterManager] registerPath:@"home/messagelist/getmessage" actionBlock:^(YTRouterActionCallbackObject *callbackObject) {
        // 內(nèi)容回調(diào)
        !callbackObject.actionCallbackBlock ?: callbackObject.actionCallbackBlock(@"message content text demo");
    }];
}

@end

客戶端調(diào)用者使用

1细移、簡(jiǎn)單的path跳轉(zhuǎn)調(diào)用
使用YTRouterManager單例對(duì)象的runWithPath方法,傳遞一個(gè)注冊(cè)的path參數(shù)完成跳轉(zhuǎn)熊锭。

[self addActionWithTitle:@"Router頁(yè)面跳轉(zhuǎn)" detailText:@"home/messagelist" callback:^{
    [[YTRouterManager sharedRouterManager] runWithPath:@"home/messagelist"];
}];

2弧轧、使用URL調(diào)用和有URL參數(shù)的調(diào)用
使用YTRouterManager單例對(duì)象的runWithURLString方法,傳遞一個(gè)完整的包含了scheme/path碗殷,或者有參數(shù)的會(huì)才有參數(shù)的URL精绎,比如"YTRouter://home/messagelist""YTRouter://home/messagelist?title=Hello Message" ,路由組件會(huì)解析出里面的scheme锌妻、path代乃、params,進(jìn)行scheme過(guò)濾處理仿粹、path查詢YTRouterActionObject對(duì)象處理搁吓、參數(shù)傳遞處理。

[self addActionWithTitle:@"Router使用URL調(diào)用" detailText:@"YTRouter://home/messagelist" callback:^{
    [[YTRouterManager sharedRouterManager] runWithURLString:@"YTRouter://home/messagelist"];
}];

[self addActionWithTitle:@"Router使用帶參數(shù)的URL調(diào)用" detailText:@"YTRouter://home/messagelist?title=Hello Message" callback:^{
    [[YTRouterManager sharedRouterManager] runWithURLString:@"YTRouter://home/messagelist?title=Hello Message"];
}];

效果如下圖所示:

效果圖

3吭历、簡(jiǎn)單的path跳轉(zhuǎn)調(diào)用
使用YTRouterManager單例對(duì)象的runWithActionCallbackObject方法堕仔,傳遞一個(gè)YTRouterActionCallbackObject類(lèi)型的參數(shù),設(shè)置YTRouterActionCallbackObject對(duì)象的uri和結(jié)果回調(diào)actionCallbackBlock參數(shù)晌区,在actionCallbackBlock中處理返回值摩骨。

[self addActionWithTitle:@"Router獲取返回值" detailText:@"home/messagelist/getmessage" callback:^{
    __block id message = nil;
    YTRouterActionCallbackObject *actionCallbackObject = [YTRouterActionCallbackObject new];
    actionCallbackObject.uri = [[YTUri alloc] initWithPath:@"home/messagelist/getmessage"];
    actionCallbackObject.actionCallbackBlock = ^(id result) {
        message = result;
    };
    [[YTRouterManager sharedRouterManager] runWithActionCallbackObject:actionCallbackObject];
    
    NSLog(@"message = %@", message);
}];

一些小想法

  • load方法中注冊(cè)path對(duì)性能有一定的影響,如果這里會(huì)成為性能瓶頸朗若,考慮把這部分分代碼放在對(duì)象方法中初始化恼五,比如主模塊發(fā)送消息給各個(gè)模塊,然后在各個(gè)模塊中處理注冊(cè)
  • YTRouterActionObject 如果需要更高的細(xì)嫩哭懈,可以考慮把path參數(shù)解析為components進(jìn)行緩存灾馒,這是一種以空間換時(shí)間的策略
  • 為了提高查找的效率,使用Dictionary而不是數(shù)組保存RouterActionObject

參考資料

iOS應(yīng)用架構(gòu)談 組件化方案
iOS組件化實(shí)踐方案-LDBusMediator煉就
iOS組件化思路-大神博客研讀和思考
iOS 組件化方案探索
蘑菇街 App 的組件化之路
NSRecursiveLock遞歸鎖的使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遣总,一起剝皮案震驚了整個(gè)濱河市你虹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彤避,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夯辖,死亡現(xiàn)場(chǎng)離奇詭異琉预,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蒿褂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)圆米,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卒暂,“玉大人,你說(shuō)我怎么就攤上這事娄帖∫察簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵近速,是天一觀的道長(zhǎng)诈嘿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)削葱,這世上最難降的妖魔是什么奖亚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮析砸,結(jié)果婚禮上昔字,老公的妹妹穿的比我還像新娘。我一直安慰自己首繁,他們只是感情好作郭,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著弦疮,像睡著了一般夹攒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挂捅,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天芹助,我揣著相機(jī)與錄音,去河邊找鬼闲先。 笑死状土,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伺糠。 我是一名探鬼主播蒙谓,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼训桶!你這毒婦竟也來(lái)了累驮?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舵揭,失蹤者是張志新(化名)和其女友劉穎谤专,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體午绳,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡置侍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜡坊。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杠输,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秕衙,到底是詐尸還是另有隱情蠢甲,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布据忘,位于F島的核電站鹦牛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏若河。R本人自食惡果不足惜能岩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萧福。 院中可真熱鬧拉鹃,春花似錦、人聲如沸鲫忍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悟民。三九已至坝辫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間射亏,已是汗流浹背近忙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留智润,地道東北人及舍。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像窟绷,于是被迫代替她去往敵國(guó)和親锯玛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359