HHRouter源码分析

511 阅读2分钟

HHRouter作为一种URL进行控制器之间跳转的一个第三方库,其代码加起来也不到300行,简单明了。

HHRouter.h

可以看注释

#import <Foundation/Foundation.h>

typedef NS_ENUM (NSInteger, HHRouteType) {
    HHRouteTypeNone = 0,
    HHRouteTypeViewController = 1,
    HHRouteTypeBlock = 2
};

typedef id (^HHRouterBlock)(NSDictionary *params);

@interface HHRouter : NSObject
//单例
+ (instancetype)shared;
//注册控制器与url
- (void)map:(NSString *)route toControllerClass:(Class)controllerClass;
//过期了,就是下面这个
- (UIViewController *)match:(NSString *)route __attribute__((deprecated));
//根据路由返回控制器
- (UIViewController *)matchController:(NSString *)route;
//注册block与url
- (void)map:(NSString *)route toBlock:(HHRouterBlock)block;
//根据路由返回block
- (HHRouterBlock)matchBlock:(NSString *)route;
//执行block
- (id)callBlock:(NSString *)route;
//是哪种路由
- (HHRouteType)canRoute:(NSString *)route;

@end

//控制器一个分类,关联一个字典属性
@interface UIViewController (HHRouter)

@property (nonatomic, strong) NSDictionary *params;

@end

HHRouter.m

我先注册[[HHRouter shared] map:@"/user/:userId/" toControllerClass:[UserViewController class]];

- (void)map:(NSString *)route toBlock:(HHRouterBlock)block
{
    NSMutableDictionary *subRoutes = [self subRoutesToRoute:route];

    subRoutes[@"_"] = [block copy];
}
- (NSMutableDictionary *)subRoutesToRoute:(NSString *)route
{
//@[user,
//:userId]    
NSArray *pathComponents = [self pathComponentsFromRoute:route];

    NSInteger index = 0;
    NSMutableDictionary *subRoutes = self.routes;
/**
这里面赋值比较有意思,没有这个key的时候,增加一个key,value为一个新的字典,在将原来字典指针指向新的字典,循环执行
{
    user =     {
        ":userId" =         {
            "_" = UserViewController;
        };
    };
}
*/
    while (index < pathComponents.count) {
        NSString *pathComponent = pathComponents[index];
        if (![subRoutes objectForKey:pathComponent]) {
            subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
        }
        subRoutes = subRoutes[pathComponent];
        index++;
    }
    
    return subRoutes;
}
- (NSArray *)pathComponentsFromRoute:(NSString *)route
{
    NSMutableArray *pathComponents = [NSMutableArray array];
    ///user/:userId/
    NSURL *url = [NSURL URLWithString:[route stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    
    for (NSString *pathComponent in url.path.pathComponents) {
        if ([pathComponent isEqualToString:@"/"]) continue;
        if ([[pathComponent substringToIndex:1] isEqualToString:@"?"]) break;
        [pathComponents addObject:[pathComponent stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    }
    
    return [pathComponents copy];
}

匹配控制器UIViewController *viewController = [[HHRouter shared] matchController:@"/user/1/"];

- (UIViewController *)matchController:(NSString *)route
{
    NSDictionary *params = [self paramsInRoute:route];
    Class controllerClass = params[@"controller_class"];

    UIViewController *viewController = [[controllerClass alloc] init];

    if ([viewController respondsToSelector:@selector(setParams:)]) {
        [viewController performSelector:@selector(setParams:)
                             withObject:[params copy]];
    }
    return viewController;
}

- (NSDictionary *)paramsInRoute:(NSString *)route
{
    NSMutableDictionary *params = [NSMutableDictionary dictionary];

    params[@"route"] = [self stringFromFilterAppUrlScheme:route];

    NSMutableDictionary *subRoutes = self.routes;
    NSArray *pathComponents = [self pathComponentsFromRoute:params[@"route"]];
    for (NSString *pathComponent in pathComponents) {
        BOOL found = NO;
        NSArray *subRoutesKeys = subRoutes.allKeys;
        for (NSString *key in subRoutesKeys) {
            if ([subRoutesKeys containsObject:pathComponent]) {
                found = YES;
                subRoutes = subRoutes[pathComponent];
                break;
            } else if ([key hasPrefix:@":"]) {
                found = YES;
                subRoutes = subRoutes[key];
                params[[key substringFromIndex:1]] = pathComponent;
                break;
            }
        }
        if (!found) {
            return nil;
        }
    }

//这是针对这种url: UIViewController *viewController = [[HHRouter shared] matchController:@"/user/1/?tabIndex=3"];  
    // Extract Params From Query.
    NSRange firstRange = [route rangeOfString:@"?"];
    if (firstRange.location != NSNotFound && route.length > firstRange.location + firstRange.length) {
        NSString *paramsString = [route substringFromIndex:firstRange.location + firstRange.length];
        NSArray *paramStringArr = [paramsString componentsSeparatedByString:@"&"];
        for (NSString *paramString in paramStringArr) {
            NSArray *paramArr = [paramString componentsSeparatedByString:@"="];
            if (paramArr.count > 1) {
                NSString *key = [paramArr objectAtIndex:0];
                NSString *value = [paramArr objectAtIndex:1];
                params[key] = value;
            }
        }
    }
    
    Class class = subRoutes[@"_"];
    if (class_isMetaClass(object_getClass(class))) {
        if ([class isSubclassOfClass:[UIViewController class]]) {
            params[@"controller_class"] = subRoutes[@"_"];
        } else {
            return nil;
        }
    } else {
        if (subRoutes[@"_"]) {
            params[@"block"] = [subRoutes[@"_"] copy];
        }
    }

    return [NSDictionary dictionaryWithDictionary:params];
}

- (NSString *)stringFromFilterAppUrlScheme:(NSString *)string
{
    //如果系统配置了scheme,就截取后面参数
    // filter out the app URL compontents.
    for (NSString *appUrlScheme in [self appUrlSchemes]) {
        if ([string hasPrefix:[NSString stringWithFormat:@"%@:", appUrlScheme]]) {
            return [string substringFromIndex:appUrlScheme.length + 2];
        }
    }

    return string;
}

block原理差不多,使用demo如下

[[HHRouter shared] map:@"aaaa/:hehe" toBlock:^id(NSDictionary *params) {
        UserViewController *vc = [UserViewController new];
        vc.params = params;
        return vc;
    }];
    HHRouterBlock block = [[HHRouter shared] matchBlock:@"/aaaa/1/"];
    UserViewController *blockVc = block(@{});

/**
(lldb) po blockVc.params
{
    block = "<__NSGlobalBlock__: 0x10258f288>";
    hehe = 1;
    route = "/aaaa/1/";
}

*/

这里面也没什么算法之类的,就是注册类,block,在内存中使用字典保存,取值遍历都是使用原生方法,效率也该可以的,就是项目大了需要路由的所有控制器都要提前注册,内存也该也还好,就是一个dictiona。启动速度的如果不是首要路由,也可延后注册。