读组件化之MGJRouter源码第一次的收获与思考

之前项目一直在做组件化,但是一直没有弄懂里面的原理,刨根问底想得又少,还是先看看人家是怎么实现的吧

源码地址:https://github.com/meili/MGJRouter/tree/0.8.0

开始我们先创建一个导航栏控制器,方便Demo跳转,否则跳转不了页面急的蛋疼。

读源码还是要有点基础的,不然看的头会眼花,尤其是不写注释,或者用一些阅读人没有用过的API。

首先我们先创建两个ViewController:读组件化之MGJRouter源码第一次的收获与思考

#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 版本 看更新了什么

栏目
每日精选