讀組件化之MGJRouter源碼第一次的收穫與思考

以前項目一直在作組件化,可是一直沒有弄懂裏面的原理,刨根問底想得又少,仍是先看看人家是怎麼實現的吧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

相關文章
相關標籤/搜索