之前项目一直在做组件化,但是一直没有弄懂里面的原理,刨根问底想得又少,还是先看看人家是怎么实现的吧
源码地址:https://github.com/meili/MGJRouter/tree/0.8.0
开始我们先创建一个导航栏控制器,方便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 是一个单例,我们先不写单例,来看看会有什么问题?
问题来了:
@implementation DIYMGJRouter
+ (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler{
[self addurl];
}
- (void)addURLPattern:(NSString *)URLPattern andHandler:(MGJRouterHandler)handler {
}
@end
我要在一个类方法里去调用一个实例方法,这个时候就调用不到了,MGJ作者用的是一个单例,然后来完成这样的调用,我们也来先模仿一下吧,如果有什么不妥的地方 再改进超越。
#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 版本 看更新了什么