【源码阅读】JLRoutes

介绍

JLRoutes是一个URL解析库,可以很方便的处理不同URL schemes以及解析它们的参数,并通过回调block来处理URL对应的操作。

使用场景

对一个App中单独的模块,可以使用openURL的方式进行页面跳转,很好地解耦不同的模块,蘑菇街的组件化之路就是基于URL跳转的方式,当然casa也提出了Target—Action模式下配合category实现的组件化架构,时隔几个月又重写看了两位大神的文章,感觉脑细胞真的不够用啊。

我没有组件化的经验,所以使用JSRoutes仅限于远程调用(服务端下发、Push跳转等),本地调用还是不太敢用这种URL的统一跳转。

使用实例

先来个简单的使用demo

在didFinishLaunchingWithOptions中注册所有的URL

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [JLRoutes addRoute:@"/:controller" handler:^BOOL(NSDictionary *parameters) {
        NSString *controller = parameters[@"controller"];
        
        [self.window.rootViewController presentViewController:[[NSClassFromString(controller) alloc] init] animated:YES completion:^{
            
        }];
        return YES;
    }];
    return YES;
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
  return [JLRoutes routeURL:url];
}

打开指定URL资源

NSURL *viewUserURL = [NSURL URLWithString:@"myapp://user/view/joeldev"];
[[UIApplication sharedApplication] openURL:viewUserURL];

原理

JLRoutes本质可以理解为:保存一个全局的Map,key是url,value是对应的block,url和block都会常驻在内存中,这也是为什么casa反对使用URL跳转来实现组件化的原因,当注册的url很多了,对内存的消耗也是很大的。当打开一个URL时,JLRoutes就可以遍历这个全局的map,通过url来执行对应的block。

内部实现

namespace

routeControllersMap是一个NSDictionary类型的单例,key是namespace,value是一个array,里面包含当前namespace下所有的routes。

命名空间也对应我们的URL scheme。

globalRoutes返回全局命名空间

+ (instancetype)globalRoutes {
    return [self routesForScheme:kJLRoutesGlobalNamespaceKey];
}

routesForScheme返回指定scheme对应的命名空间,如果不存在就创建一个


+ (instancetype)routesForScheme:(NSString *)scheme {
    JLRoutes *routesController = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        routeControllersMap = [[NSMutableDictionary alloc] init];
    });
    
    if (!routeControllersMap[scheme]) {
        routesController = [[self alloc] init];
        routesController.namespaceKey = scheme;
        routeControllersMap[scheme] = routesController;
    }
    
    routesController = routeControllersMap[scheme];
    
    return routesController;
}
添加Route

注册一个Route到global scheme namespace,并设置其优先级(默认优先级是0),block返回一个bool,如果返回YES表示当前匹配成功,如果返回NO表示继续匹配其他Route(_JLRoute对象)

一个内部类,用下划线开头命名_JLRoute,其结构如下:

@interface _JLRoute : NSObject

@property (nonatomic, weak) JLRoutes *parentRoutesController;
@property (nonatomic, strong) NSString *pattern;
@property (nonatomic, strong) BOOL (^block)(NSDictionary *parameters);
@property (nonatomic, assign) NSUInteger priority;
@property (nonatomic, strong) NSArray *patternPathComponents;

- (NSDictionary *)parametersForURL:(NSURL *)URL components:(NSArray *)URLComponents;

@end

addRoute这个方法对原始routePattern字符串做一个加工和过滤的操作,如去掉圆括号

- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary *parameters))handlerBlock {
    
    // if there's a pair of parenthesis, process optionals, trim the parenthesis, put it on trimmedRoute
    NSString *trimmedRoute = routePattern;
    
    // repeat until no parenthesis pair is found
    while ([trimmedRoute rangeOfString:@")" options:NSBackwardsSearch].location > [trimmedRoute rangeOfString:@"(" options:NSBackwardsSearch].location) {
        
        //Build route with the optionals
        NSString *patternWithOptionals = [trimmedRoute stringByReplacingOccurrencesOfString:@"(" withString:@""];
        patternWithOptionals = [patternWithOptionals stringByReplacingOccurrencesOfString:@")" withString:@""];
        [self registerRoute:patternWithOptionals priority:priority handler:handlerBlock];
        
        //Build route without optionals
        NSRange rangeOfLastParentheses = [trimmedRoute rangeOfString:@"(" options:NSBackwardsSearch];
        NSRange rangeToRemove = NSMakeRange(rangeOfLastParentheses.location, trimmedRoute.length - rangeOfLastParentheses.location);
        NSString *patternWithLastOptionalRemoved = [trimmedRoute stringByReplacingCharactersInRange:rangeToRemove withString:@""];
        //Remove any parenthesis for other optionals that might still be in the route
        NSString *patternWithoutOptionals = [patternWithLastOptionalRemoved stringByReplacingOccurrencesOfString:@"(" withString:@""];
        patternWithoutOptionals = [patternWithoutOptionals stringByReplacingOccurrencesOfString:@")" withString:@""];
        [self registerRoute:patternWithoutOptionals priority:priority handler:handlerBlock];
        
        trimmedRoute = patternWithLastOptionalRemoved;
    }
    
    //Only register original route if trimmedRoute haven't been modified.
    if (trimmedRoute == routePattern) {
        [self registerRoute:routePattern priority:priority handler:handlerBlock];
    }
}

registerRoute这个方法就是把_JLRoute插入到routeControllersMap[kJLRoutesGlobalNamespaceKey]这个list中的时候,用到了插入排序的思想,priority高的在前面。这样enum这个_JLRoute List的时候,如果match pattern,就return,自然就解决了「路径匹配优先级」的问题。

- (void)registerRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary *parameters))handlerBlock {
    _JLRoute *route = [[_JLRoute alloc] init];
    route.pattern = routePattern;
    route.priority = priority;
    route.block = [handlerBlock copy];
    route.parentRoutesController = self;
    
    if (!route.block) {
        route.block = [^BOOL (NSDictionary *params) {
            return YES;
        } copy];
    }
    
    if (priority == 0 || self.routes.count == 0) {
        [self.routes addObject:route];
    } else {
        NSArray *existingRoutes = self.routes;
        NSUInteger index = 0;
        BOOL addedRoute = NO;
        
        // search through existing routes looking for a lower priority route than this one
        // 找到一个优先级低的,采用插入排序
        for (_JLRoute *existingRoute in existingRoutes) {
            if (existingRoute.priority < priority) {
                // if found, add the route after it
                [self.routes insertObject:route atIndex:index];
                addedRoute = YES;
                break;
            }
            index++;
        }
        
        // if we weren't able to find a lower priority route, this is the new lowest priority route (or same priority as self.routes.lastObject) and should just be added
        if (!addedRoute)
            [self.routes addObject:route];
    }
}

你可能感兴趣的:(【源码阅读】JLRoutes)