以前項目一直在作組件化,可是一直沒有弄懂裏面的原理,刨根問底想得又少,仍是先看看人家是怎麼實現的吧git
源碼地址:https://github.com/meili/MGJRouter/tree/0.8.0github
開始咱們先建立一個導航欄控制器,方便Demo跳轉,不然跳轉不了頁面急的蛋疼。數組
讀源碼仍是要有點基礎的,否則看的頭會眼花,尤爲是不寫註釋,或者用一些閱讀人沒有用過的API。數據結構
首先咱們先建立兩個ViewController:組件化
#import <UIKit/UIKit.h> typedef UIViewController *(^ViewControllerHandler)(); @interface DemoListViewController : UIViewController + (void)registerWithTitle:(NSString *)title handler:(UIViewController *(^)())handler; @end #import "DemoListViewController.h" static NSMutableDictionary *titleWithHandlers; static NSMutableArray *titles; @interface DemoListViewController ()<UITableViewDataSource, UITableViewDelegate> @property (nonatomic) UITableView *tableView; @end @implementation DemoListViewController //經過+load方法,建立多個測試的VC控制器,而後調用該方法來管理和存儲 + (void)registerWithTitle:(NSString *)title handler:(UIViewController *(^)())handler{ if (!titleWithHandlers){ titleWithHandlers = [NSMutableDictionary new]; titles = [NSMutableArray array]; } [titles addObject:title]; titleWithHandlers[title] = handler; } - (void)viewDidLoad { [super viewDidLoad]; self.title = @"SFC_MGJRouterDemo"; self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; self.tableView.delegate = self; self.tableView.dataSource = self; [self.view addSubview:self.tableView]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return titleWithHandlers.allKeys.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.textLabel.text = titles[indexPath.row]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; UIViewController *viewController = ((ViewControllerHandler)titleWithHandlers[titles[indexPath.row]])(); [self.navigationController pushViewController:viewController animated:YES]; } @end #import "DemoDetailViewController.h" #import "DemoListViewController.h" @interface DemoDetailViewController () @property (nonatomic) SEL selectedSelector; @end @implementation DemoDetailViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor colorWithRed:239.f/255 green:239.f/255 blue:244.f/255 alpha:1]; } //好奇怪啊 這個類我都沒用,這個方法它居然自動走了。並且是最新走的 //回答參考:https://www.jianshu.com/p/816e510dc1dd + (void)load { NSLog(@"load...."); DemoDetailViewController *detailViewController = [[DemoDetailViewController alloc] init]; [DemoListViewController registerWithTitle:@"基本使用" handler:^UIViewController *{ detailViewController.selectedSelector = @selector(demoBasicUsage); return detailViewController; }]; } /// 視圖已徹底過渡到屏幕上時調用,來觸發視圖徹底顯示在屏幕上以後的行爲,例如任何動畫。 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; //一進來主動調用當前類的SEL方法 [self performSelector:self.selectedSelector withObject:nil afterDelay:0]; } - (void)demoBasicUsage{ //重點代碼在這裏 } @end
上面都是準備工做,接下來看重要的:測試
首先看 MGJRouter 是一個單例,咱們先不寫單例,來看看會有什麼問題?動畫
問題來了:atom
@implementation DIYMGJRouter + (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler{ [self addurl]; } - (void)addURLPattern:(NSString *)URLPattern andHandler:(MGJRouterHandler)handler { } @end
我要在一個類方法裏去調用一個實例方法,這個時候就調用不到了,MGJ做者用的是一個單例,而後來完成這樣的調用,咱們也來先模仿一下吧,若是有什麼不妥的地方 再改進超越。url
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN typedef void (^MGJRouterHandler)(NSDictionary *routerParameters); @interface DIYMGJRouter : NSObject + (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler; + (instancetype)sharedInstance; /** * 打開此 URL * 會在已註冊的 URL -> Handler 中尋找,若是找到,則執行 Handler * * @param URL 帶 Scheme,如 mgj://beauty/3 */ + (void)openURL:(NSString *)URL; /** * 打開此 URL,同時當操做完成時,執行額外的代碼 * * @param URL 帶 Scheme 的 URL,如 mgj://beauty/4 * @param completion URL 處理完成後的 callback,完成的斷定跟具體的業務相關 */ + (void)openURL:(NSString *)URL completion:(void (^)(void))completion; /** * 打開此 URL,帶上附加信息,同時當操做完成時,執行額外的代碼 * * @param URL 帶 Scheme 的 URL,如 mgj://beauty/4 * @param parameters 附加參數 * @param completion URL 處理完成後的 callback,完成的斷定跟具體的業務相關 */ + (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(void))completion; /** * 保存了全部已註冊的 URL * 結構相似 @{@"beauty": @{@":id": {@"_", [block copy]}}} */ @property (nonatomic) NSMutableDictionary *routes; @end NS_ASSUME_NONNULL_END #import "DIYMGJRouter.h" static NSString * const MGJ_ROUTER_WILDCARD_CHARACTER = @"~"; NSString *const MGJRouterParameterURL = @"MGJRouterParameterURL"; NSString *const MGJRouterParameterCompletion = @"MGJRouterParameterCompletion"; NSString *const MGJRouterParameterUserInfo = @"MGJRouterParameterUserInfo"; @implementation DIYMGJRouter + (instancetype)sharedInstance{ static DIYMGJRouter *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //... 不該該是這樣寫麼 DIYMGJRouter alloc. instance = [[self alloc] init]; }); return instance; } + (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler{ [[self sharedInstance] addURLPattern:URLPattern andHandler:handler]; } - (void)addURLPattern:(NSString *)URLPattern andHandler:(MGJRouterHandler)handler { NSLog(@"傳入的URLPattern:%@ 剛進來的self.routes:%@",URLPattern,self.routes); /* 剛進來的self.routes:{ } */ //將傳入的url字符串經過必定的格式切割成一個數組 NSArray *pathComponents = [self pathComponentsFromURL:URLPattern]; //設置一個索引初始值爲 0 NSInteger index = 0; //建立一個臨時的字典保存了全部已註冊的 URL NSMutableDictionary *subRoutes = self.routes; //while循環 每循環一次 index + 1 ,直到遍歷完 pathComponents 數組的值 while (index < pathComponents.count) { // NSLog(@"while index:%d",index); NSString * pathComponent = pathComponents[index]; //拿每個取出的字符串到 已註冊的URL字典裏獲取,若是獲取不到 就用當前的字符串作key , value爲一個新的可變空字典 if (![subRoutes objectForKey:pathComponent]){ subRoutes[pathComponent] = [[NSMutableDictionary alloc] init]; } //這塊做者有點裝逼了 一會 objectforkey 一會這樣取,也不知道是啥用途,嘗試獲取剛纔設置的空字典? subRoutes = subRoutes[pathComponent]; NSLog(@"subRoutes:%@",subRoutes); //while循環須要手動加index的值 index ++; } //判斷當前傳進來的block是否爲空 if (handler){ //若是不爲空的話 設置字典 key 爲 下劃線_ ,value爲傳進來的 block subRoutes[@"_"] = [handler copy]; NSLog(@"if (handler):%@",subRoutes); } NSLog(@"方法執行完畢的self.routes:%@",self.routes); /* 方法執行完畢的self.routes:{ mgj = { "~" = { category = { travel = { "_" = "<__NSGlobalBlock__: 0x1003f4158>"; }; }; }; }; } */ } //來自URL的路徑組件 - (NSArray*)pathComponentsFromURL:(NSString*)URL { //1.建立一個路徑組件的數組 NSMutableArray *pathComponents = [NSMutableArray array]; //2.判斷url 是否包含 :// 格式 if ([URL rangeOfString:@"://"].location != NSNotFound){ //3.若是不包含 :// (協議格式) //4.根據協議格式,將URL字符串裏包含協議格式的遇到就分割轉換爲array數組裏的元素 NSArray *pathSegments = [URL componentsSeparatedByString:@"://"]; //5.若是 URL 包含協議,那麼把協議做爲第一個元素放進去 [pathComponents addObject:pathSegments[0]]; //6.判斷若是隻有協議,那麼放一個佔位符 if ((pathSegments.count == 2 && ((NSString *)pathSegments[1]).length) || pathSegments.count < 2) { [pathComponents addObject:MGJ_ROUTER_WILDCARD_CHARACTER]; } NSLog(@"pathComponents:%@",pathComponents); //7.返回一個字符串,這個字符串截取接受對象字符串範圍是給定索引index到這個字符串的結尾 //好比 mgj://foo/bar 這個串, 下面格式location後加4的話 打印出來就剩下:oo/bar URL = [URL substringFromIndex:[URL rangeOfString:@"://"].location + 3]; NSLog(@"URL:%@",URL); } //遍歷 pathComponents 析構路徑,得到組成此路徑的各個部分 for (NSString *pathComponent in [[NSURL URLWithString:URL] pathComponents]){ NSLog(@"pathComponent~:%@",pathComponent); //對路徑規則的一些處理 if ([pathComponent isEqualToString:@"/"]) continue; if ([[pathComponent substringToIndex:1] isEqualToString:@"1"]) break; [pathComponents addObject:pathComponent]; } NSLog(@"最後要返回的pathComponents格式:%@",pathComponents); //可變數組這裏用copy返回 是懼怕後面修改 影響嗎?能夠測試下 return [pathComponents copy]; } - (NSMutableDictionary *)routes { if (!_routes) { _routes = [[NSMutableDictionary alloc] init]; } return _routes; } + (void)openURL:(NSString *)URL { [self openURL:URL completion:nil]; } + (void)openURL:(NSString *)URL completion:(void (^)(void))completion { [self openURL:URL withUserInfo:nil completion:completion]; } + (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(void))completion { //1.這個方法是用來進行轉碼的,即將漢字轉碼.9.0之後換了API URL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //2.返回一個參數字典 key爲 url block NSMutableDictionary *parameters = [[self sharedInstance] extractParametersFromURL:URL]; NSLog(@"遍歷字典排序前:%@",parameters); //3.遍歷字典排序 [parameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([obj isKindOfClass:[NSString class]]){ parameters[key] = [obj stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; } }]; NSLog(@"排序完:%@",parameters); //判斷參數字典是否爲空 if (parameters) { //獲取key爲block的 block MGJRouterHandler handler = parameters[@"block"]; //判斷參數 若是有的話 設置字典對應key和value值 if (completion) { parameters[MGJRouterParameterCompletion] = completion; } //把傳進來的傳參,設置到字典參數裏, 判斷參數 若是有的話 設置字典對應key和value值 if (userInfo) { parameters[MGJRouterParameterUserInfo] = userInfo; } //若是block能取到值,就刪除key爲block的值 if (handler) { [parameters removeObjectForKey:@"block"]; //經過handler block把參數傳出去 NSLog(@"1+++++++++++++"); handler(parameters); NSLog(@"走了 handler(parameters)"); } } NSLog(@"openURl走完 最後的 parameters:%@",parameters); } #pragma mark -Utils - (NSMutableDictionary *)extractParametersFromURL:(NSString *)url { //建立一個參數可變字典 NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; //把url賦值給 key 爲 MGJRouterParameterURL 的值 parameters[MGJRouterParameterURL] = url; //將已經存在的URL字典賦值給臨時字典 NSMutableDictionary *subRoutes = self.routes; //將傳入的url字符串經過必定的格式切割成一個數組 NSArray *pathComponents = [self pathComponentsFromURL:url]; // borrowed from HHRouter(https://github.com/Huohua/HHRouter) //遍歷切割後的字符串數組 for (NSString *pathComponent in pathComponents) { //聲明一個found默認值爲NO BOOL found = NO; //對 key 進行排序,這樣能夠把 ~ 放到最後 NSArray *subRoutesKeys = [subRoutes.allKeys sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { // return [obj1 compare:obj2]; }]; //遍歷keys for (NSString *key in subRoutesKeys) { //判斷 key 是否和已經分割過數組裏的當前遍歷字符串相等,或者等於 ~ 號 if ([key isEqualToString:pathComponent] || [key isEqualToString:MGJ_ROUTER_WILDCARD_CHARACTER]){ //若是等於的話就是找到了 found = YES; NSLog(@"賦值前:%@",subRoutes); subRoutes = subRoutes[key]; NSLog(@"賦值後:%@",subRoutes); /* 賦值前:{ foo = { bar = { "_" = "<__NSGlobalBlock__: 0x10da93138>"; }; }; } 賦值後:{ bar = { "_" = "<__NSGlobalBlock__: 0x10da93138>"; }; } */ break; } else if ([key hasPrefix:@":"]){ found = YES; subRoutes = subRoutes[key]; parameters[[key substringFromIndex:1]] = pathComponent; break; } } NSLog(@"extractParametersFromURL裏的parameters:%@",parameters); //若是最沒有找到該 pathComponent 對應的 handler, 則以上一層的 handler 做爲 fallback if (!found && !subRoutes[@"_"]){ return nil; } } //Extract ParamsFrom Query. NSArray *pathInfo = [url componentsSeparatedByString:@"?"]; if (pathInfo.count > 1){ // NSString *parametersString = [pathInfo objectAtIndex:1]; // NSArray *paramStringArr = [parametersString componentsSeparatedByString:@"&"]; // for (NSString *paramString in paramStringArr){ NSArray* paramArr = [paramString componentsSeparatedByString:@"="]; if (paramArr.count > 1) { NSString* key = [paramArr objectAtIndex:0]; NSString* value = [paramArr objectAtIndex:1]; parameters[key] = value; } } } // if (subRoutes[@"_"]) { parameters[@"block"] = [subRoutes[@"_"] copy]; } NSLog(@"extractParametersFromURL最終的parameters:%@",parameters); /* 最終的parameters:{ MGJRouterParameterURL = "mgj://foo/bar"; block = "<__NSGlobalBlock__: 0x1052b9138>"; } */ return parameters; } #import "DemoDetailViewController.h" #import "DemoListViewController.h" #import "DIYMGJRouter.h" @interface DemoDetailViewController () @property (nonatomic) SEL selectedSelector; @end @implementation DemoDetailViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor colorWithRed:239.f/255 green:239.f/255 blue:244.f/255 alpha:1]; } //好奇怪啊 這個類我都沒用,這個方法它居然自動走了。並且是最新走的 //回答參考:https://www.jianshu.com/p/816e510dc1dd + (void)load { NSLog(@"load...."); DemoDetailViewController *detailViewController = [[DemoDetailViewController alloc] init]; [DemoListViewController registerWithTitle:@"基本使用" handler:^UIViewController *{ detailViewController.selectedSelector = @selector(demoBasicUsage); return detailViewController; }]; [DemoListViewController registerWithTitle:@"有參數使用" handler:^UIViewController *{ detailViewController.selectedSelector = @selector(demoParaUsage); return detailViewController; }]; } /// 視圖已徹底過渡到屏幕上時調用,來觸發視圖徹底顯示在屏幕上以後的行爲,例如任何動畫。 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; //一進來主動調用當前類的SEL方法 [self performSelector:self.selectedSelector withObject:nil afterDelay:0]; } - (void)demoBasicUsage{ //重點代碼1 在這裏. //在組件裏面去註冊。註冊裏的回調去作一些相應的跳轉及處理,待測試 //把傳進去的url路徑,切割成一層一層的數據結構 像目錄同樣 key value key value 一層套一層 //最後一層是 下劃線 _ 對應的是Handler的block 等待 open的時候去調用 [DIYMGJRouter registerURLPattern:@"mgj://foo/bar" toHandler:^(NSDictionary *routerParameters) { NSLog(@"2+++++++++++++"); NSLog(@"匹配到了 url,如下是相關信息:routerParameters:%@",routerParameters); }]; //在主項目裏用的時候去打開調用..思考關於組件化會遇到的問題 // 重點代碼2 在這裏 //把url路徑穿進去,到已經註冊的內存字典裏去一層一層的拆開. 若是能取到block就走註冊裏的回調block. [DIYMGJRouter openURL:@"mgj://foo/bar"]; //中文匹配 // [DIYMGJRouter registerURLPattern:@"mgj://category/家居" toHandler:^(NSDictionary *routerParameters) { // NSLog(@"中文匹配到了 url,如下是相關信息:routerParameters:%@",routerParameters); // // }]; // [DIYMGJRouter openURL:@"mgj://category/家居"]; } - (void)demoParaUsage { [DIYMGJRouter registerURLPattern:@"mgj://category/travel" toHandler:^(NSDictionary *routerParameters) { NSLog(@"有參數匹配到了 url,如下是相關信息:routerParameters:%@",routerParameters); }]; [DIYMGJRouter openURL:@"mgj://category/travel" withUserInfo:@{@"user_id":@2000} completion:nil]; } @end
接下來更新下一個tag 版本 看更新了什麼spa