以前项目一直在作组件化,可是一直没有弄懂里面的原理,刨根问底想得又少,仍是先看看人家是怎么实现的吧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