MGJRouter代碼分析

今天來聊聊組件化保屯,之前一直聽說大廠在搞,什么淘寶架構(gòu),什么蘑菇街配椭,既然談到了架構(gòu)的問題虫溜,那必屬重中之重。接下來分析一下蘑菇街開源的代碼股缸,自己做個總結(jié)衡楞。

引入

類書本的文章個人感覺還是寫不來的,再搬到自己寫的東西這來也不合適敦姻,所以直接上一鏈接瘾境,通過鏈接文章大致可了解下它的前身后世,產(chǎn)生原因镰惦,以及整體宏觀架構(gòu)設(shè)計迷守,而我接下來要做的是細(xì)化,以及轉(zhuǎn)化旺入,便于自己吸收 ---------> 組件化架構(gòu)漫談

1. 話不多說兑凿,先看入口:
image_1c25qj3m5ejq111g1v5lvkgjek9.png-7.7kB
image_1c25qj3m5ejq111g1v5lvkgjek9.png-7.7kB
@interface MGJRouter ()
/**
 *  保存了所有已注冊的 URL
 *  結(jié)構(gòu)類似 @{@"beauty": @{@":id": {@"_", [block copy]}}}
 */
@property (nonatomic) NSMutableDictionary *routes;
@end

+ (instancetype)sharedInstance
{
    static MGJRouter *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

很明顯,蘑菇街架構(gòu)(以下簡稱MGJ)通過該單例作管理茵瘾,統(tǒng)一進(jìn)行調(diào)配礼华,而該單例僅有一個變量,就是routes拗秘,實際上它僅僅是管理了一個字典的結(jié)構(gòu)圣絮,具體字典內(nèi)有哪些內(nèi)容,我們慢慢看雕旨;

2. 回調(diào)Block的定義
/**
 *  routerParameters 里內(nèi)置的幾個參數(shù)會用到上面定義的 string
 */
typedef void (^MGJRouterHandler)(NSDictionary *routerParameters);

/**
 *  需要返回一個 object扮匠,配合 objectForURL: 使用
 */
typedef id (^MGJRouterObjectHandler)(NSDictionary *routerParameters);

上面這兩個block定義是MGJ注冊URL的回調(diào),一個帶返回值凡涩,另一個不帶棒搜,在這里我們說一下帶返回值的block用法;如下舉例 ------>

WX20180107-134538@2x.png-37.5kB
WX20180107-134538@2x.png-37.5kB

//聲明
typedef UIViewController *(^ViewControllerHandler)();

//作參數(shù)
@interface DemoListViewController : UIViewController
+ (void)registerWithTitle:(NSString *)title handler:(ViewControllerHandler)handler;
@end

//定義
@implementation DemoListViewController
+ (void)registerWithTitle:(NSString *)title handler:(ViewControllerHandler)handler
{
    UIViewController* vc = handler()
}
@end

//在別處調(diào)用
@implementation DemoDetailViewController
[DemoListViewController registerWithTitle:@"基本使用" handler:^UIViewController *{
        return DemoDetailViewController();
}];
@end

如上活箕,我們把ViewControllerHandler的運(yùn)行延遲到了實際調(diào)用的時刻帮非,并且我們可以在這個handler的實現(xiàn)中帶入很多信息;

3. MGJ數(shù)據(jù)結(jié)構(gòu)管理
extern NSString *const MGJRouterParameterURL;
extern NSString *const MGJRouterParameterCompletion;
extern NSString *const MGJRouterParameterUserInfo;
//*************************************************
static NSString * const MGJ_ROUTER_WILDCARD_CHARACTER = @"~";  //這是一個占位符
static NSString *specialCharacters = @"/?&.";

NSString *const MGJRouterParameterURL = @"MGJRouterParameterURL";
NSString *const MGJRouterParameterCompletion = @"MGJRouterParameterCompletion";
NSString *const MGJRouterParameterUserInfo = @"MGJRouterParameterUserInfo";

從這里我們可以看出讹蘑,MGJ的路由管理,實際上是一個解析url以及對應(yīng)的管理筑舅,我們舉幾個URL來看一下:

@"mgj://"
@"mgj://foo/bar/none/exists"
@"mgj://foo/bar" 
@"mgj://category/家居"
@"mgj://category/travel"
@"mgj://search/:query"
@"mgj://detail"
@"mgj://search/:keyword"
@"mgj://search_top_bar"

通過上面的URL我們可以看出座慰,路由的管理實際上就是url的解析過程,下面我們來具體看一下解析過程翠拣;

4. URL解析
  • route url
WX20180107-150437@2x.png-59.1kB
WX20180107-150437@2x.png-59.1kB
- (NSArray*)pathComponentsFromURL:(NSString*)URL
{
    NSMutableArray *pathComponents = [NSMutableArray array];
    if ([URL rangeOfString:@"://"].location != NSNotFound) {
        NSArray *pathSegments = [URL componentsSeparatedByString:@"://"];
        // 如果 URL 包含協(xié)議版仔,那么把協(xié)議作為第一個元素放進(jìn)去
        [pathComponents addObject:pathSegments[0]];
        
        // 如果只有協(xié)議,那么放一個占位符
        URL = pathSegments.lastObject;
        if (!URL.length) {
            [pathComponents addObject:MGJ_ROUTER_WILDCARD_CHARACTER];
        }
    }

    for (NSString *pathComponent in [[NSURL URLWithString:URL] pathComponents]) {
        if ([pathComponent isEqualToString:@"/"]) continue;
        if ([[pathComponent substringToIndex:1] isEqualToString:@"?"]) break;
        [pathComponents addObject:pathComponent];
    }
    return [pathComponents copy];
}
  • key-value


    WX20180107-151956@2x.png-111.8kB
    WX20180107-151956@2x.png-111.8kB
- (NSMutableDictionary *)addURLPattern:(NSString *)URLPattern
{
    NSArray *pathComponents = [self pathComponentsFromURL:URLPattern];

    NSMutableDictionary* subRoutes = self.routes;
    
    for (NSString* pathComponent in pathComponents) {
        if (![subRoutes objectForKey:pathComponent]) {
            subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
        }
        subRoutes = subRoutes[pathComponent];
    }
    return subRoutes;
}
  • 核心url解析
WX20180107-160319@2x.png-147.8kB
WX20180107-160319@2x.png-147.8kB
- (NSMutableDictionary *)extractParametersFromURL:(NSString *)url
{
    NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
    
    parameters[MGJRouterParameterURL] = url;
    
    NSMutableDictionary* subRoutes = self.routes;
    NSArray* pathComponents = [self pathComponentsFromURL:url];
    
    BOOL found = NO;
    // borrowed from HHRouter(https://github.com/Huohua/HHRouter)
    for (NSString* pathComponent in pathComponents) {
        
        // 對 key 進(jìn)行排序,這樣可以把 ~ 放到最后
        NSArray *subRoutesKeys =[subRoutes.allKeys sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
            return [obj1 compare:obj2];
        }];
        
        for (NSString* key in subRoutesKeys) {
            if ([key isEqualToString:pathComponent] || [key isEqualToString:MGJ_ROUTER_WILDCARD_CHARACTER]) {
                found = YES;
                subRoutes = subRoutes[key];
                break;
            } else if ([key hasPrefix:@":"]) {
                found = YES;
                subRoutes = subRoutes[key];
                NSString *newKey = [key substringFromIndex:1];
                NSString *newPathComponent = pathComponent;
                // 再做一下特殊處理蛮粮,比如 :id.html -> :id
                if ([self.class checkIfContainsSpecialCharacter:key]) {
                    NSCharacterSet *specialCharacterSet = [NSCharacterSet characterSetWithCharactersInString:specialCharacters];
                    NSRange range = [key rangeOfCharacterFromSet:specialCharacterSet];
                    if (range.location != NSNotFound) {
                        // 把 pathComponent 后面的部分也去掉
                        newKey = [newKey substringToIndex:range.location - 1];
                        NSString *suffixToStrip = [key substringFromIndex:range.location];
                        newPathComponent = [newPathComponent stringByReplacingOccurrencesOfString:suffixToStrip withString:@""];
                    }
                }
                parameters[newKey] = newPathComponent;
                break;
            }
        }
        
        // 如果沒有找到該 pathComponent 對應(yīng)的 handler益缎,則以上一層的 handler 作為 fallback
        if (!found && !subRoutes[@"_"]) {
            return nil;
        }
    }
    
    // Extract Params From Query.
    NSArray<NSURLQueryItem *> *queryItems = [[NSURLComponents alloc] initWithURL:[[NSURL alloc] initWithString:url] resolvingAgainstBaseURL:false].queryItems;
    
    for (NSURLQueryItem *item in queryItems) {
        parameters[item.name] = item.value;
    }

    if (subRoutes[@"_"]) {
        parameters[@"block"] = [subRoutes[@"_"] copy];
    }
    
    return parameters;
}
+ (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion
{
    URL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSMutableDictionary *parameters = [[self sharedInstance] extractParametersFromURL:URL];
    
    [parameters enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
        if ([obj isKindOfClass:[NSString class]]) {
            parameters[key] = [obj stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        }
    }];
    
    if (parameters) {
        MGJRouterHandler handler = parameters[@"block"];
        if (completion) {
            parameters[MGJRouterParameterCompletion] = completion;
        }
        if (userInfo) {
            parameters[MGJRouterParameterUserInfo] = userInfo;
        }
        //所以注冊路由時的回調(diào)是在這里才調(diào)用到的
        //也就是openURL響應(yīng)了register的回調(diào)
        if (handler) {
            [parameters removeObjectForKey:@"block"];
            handler(parameters);
        }
    }
}
5. 后續(xù)?
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末然想,一起剝皮案震驚了整個濱河市莺奔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌变泄,老刑警劉巖令哟,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妨蛹,居然都是意外死亡屏富,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蛙卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狠半,“玉大人,你說我怎么就攤上這事颤难∩衲辏” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵乐严,是天一觀的道長瘤袖。 經(jīng)常有香客問我,道長昂验,這世上最難降的妖魔是什么捂敌? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮既琴,結(jié)果婚禮上占婉,老公的妹妹穿的比我還像新娘。我一直安慰自己甫恩,他們只是感情好逆济,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著磺箕,像睡著了一般奖慌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上松靡,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天简僧,我揣著相機(jī)與錄音,去河邊找鬼雕欺。 笑死岛马,一個胖子當(dāng)著我的面吹牛棉姐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啦逆,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼伞矩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了夏志?” 一聲冷哼從身側(cè)響起乃坤,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盲镶,沒想到半個月后侥袜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡溉贿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年枫吧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宇色。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡九杂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宣蠕,到底是詐尸還是另有隱情例隆,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布抢蚀,位于F島的核電站镀层,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏皿曲。R本人自食惡果不足惜唱逢,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屋休。 院中可真熱鬧坞古,春花似錦、人聲如沸劫樟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叠艳。三九已至奶陈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間附较,已是汗流浹背尿瞭。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留翅睛,地道東北人声搁。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像捕发,于是被迫代替她去往敵國和親疏旨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容