组件化初探 - JLRoutes源码分析

作者: 李连毛 | 来源:发表于2017-11-13 18:59 被阅读252次

最近在学习组件化的知识,看到了JLRoutes这个框架,这是一个基于路由匹配的框架,我大致读了这个框架的源码。下面就把我读的新的和大家分享一下。主要分三个部分,第一部分介绍框架结构,第二部分介绍核心代码,第三部分分析优劣。

1 框架结构

JLRoutes主要是几个文件:JLRoutesJLRRouteDefinitionUtilsJLRRouteRequestJLRRouteResponse

JLRoutes结构.png

最近在halfrost的博客中看到他整理的一个数据结构,觉得非常好,对于讲解一个框架类与类之间的关于很有帮助,这里贴我也出来,大家一定会一目了然。

JLRoutes_数据存储.jpeg

2 核心代码

2.1 routesForScheme函数

第一个核心函数是routesForScheme函数,这个函数是根据scheme找对应的routesrouteControllersMap是一个字典,keyscheme,也就是命名空间;value是一个JLRoutes实例,这个JLRoutes有个成员变量叫mutableRoutes,存放所有的这个命名空间下的routes

+ (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.scheme = scheme;
        routeControllersMap[scheme] = routesController;
    }
    
    routesController = routeControllersMap[scheme];
    
    return routesController;
}

2.2 addRoute函数

第二个核心函数就是addRoute了。函数分四部分,第一部分是处理可变的参数,第二部分是创建一个自定义的实例JLRRouteDefinition,第三部分处理一下可变参数的路由注册问题(遍历所有可能注册),第四部分注册路由。这四部分在接下来都会分开讲。

- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary<NSString *, id> *parameters))handlerBlock
{
    #1
    NSArray <NSString *> *optionalRoutePatterns = [JLRParsingUtilities expandOptionalRoutePatternsForPattern:routePattern];
    #2
    JLRRouteDefinition *route = [[JLRRouteDefinition alloc] initWithScheme:self.scheme pattern:routePattern priority:priority handlerBlock:handlerBlock];
    
    #3
    if (optionalRoutePatterns.count > 0) {
        // there are optional params, parse and add them
        for (NSString *pattern in optionalRoutePatterns) {
            [self _verboseLog:@"Automatically created optional route: %@", route];
            JLRRouteDefinition *optionalRoute = [[JLRRouteDefinition alloc] initWithScheme:self.scheme pattern:pattern priority:priority handlerBlock:handlerBlock];
            [self _registerRoute:optionalRoute];
        }
        return;
    }
    
    #4
    [self _registerRoute:route];
}

2.2.1 第一部分

先讲第一个部分,第一个函数大概的意思就是:如果来了一种带括号的url:/path/:thing/(/a)(/b)(/c),我们把它拆分组合成如下一个array。

/path/:thing/a/b/c
/path/:thing/a/b
/path/:thing/a/c
/path/:thing/b/a
/path/:thing/a
/path/:thing/b
/path/:thing/c
  1. 第一个函数用的NSScanner,其中有个比较关键的方法就是scanUpToString:intoString:,截取到指定字符串用的,把参数都截取下来。
  2. 第二个函数显而易见就是组合,将()里可能出现的情况进行排列组合。
  3. 这个函数我的理解是URL中存在可选参数,我们这里进行排列组合,存放起来,后期来什么URL到这个数组里进行匹配就行了。
+ (NSArray <NSString *> *)expandOptionalRoutePatternsForPattern:(NSString *)routePattern
{
    routePattern = @"/push/:controller/(/a)(/b)(/c)";
    if ([routePattern rangeOfString:@"("].location == NSNotFound) {
        return @[];
    }
    
    NSString *baseRoute = nil;
    NSArray *components = [self _optionalComponentsForPattern:routePattern baseRoute:&baseRoute];
    NSArray *routes = [self _routesForOptionalComponents:components baseRoute:baseRoute];
    
    return routes;
}

一个函数都把一个文件讲完了😀

2.2.2 第二部分

第二个函数很简单,初始化了一个用来存放route的实例,实例里有scheme命名空间,pattern模式,priority优先级,和要执行的handlerBlock

- (instancetype)initWithScheme:(NSString *)scheme pattern:(NSString *)pattern priority:(NSUInteger)priority handlerBlock:(BOOL (^)(NSDictionary *parameters))handlerBlock
{
    if ((self = [super init])) {
        self.scheme = scheme;
        self.pattern = pattern;
        self.priority = priority;
        self.handlerBlock = handlerBlock;
        
        if ([pattern characterAtIndex:0] == '/') {
            pattern = [pattern substringFromIndex:1];
        }
        
        self.patternComponents = [pattern componentsSeparatedByString:@"/"];
    }
    return self;
}

2.2.3 第三部分

2.2.4 第四部分

第四部分是路由注册,这个函数很简单,如果在某个scheme命名空间下,如果从没有注册过,addObject:之,如果有路由存在,遍历一下,根据优先级插入。

- (void)_registerRoute:(JLRRouteDefinition *)route
{
    if (route.priority == 0 || self.mutableRoutes.count == 0) {
        [self.mutableRoutes addObject:route];
    } else {
        NSUInteger index = 0;
        BOOL addedRoute = NO;
        
        // search through existing routes looking for a lower priority route than this one
        for (JLRRouteDefinition *existingRoute in [self.mutableRoutes copy]) {
            if (existingRoute.priority < route.priority) {
                // if found, add the route after it
                [self.mutableRoutes 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.mutableRoutes addObject:route];
        }
    }
}

这里总结一下,一个scheme对应一个JLRoute实例,我们可以为这个JLRoute注册很多中URLPattern,我们现在如果需要openURL,那么会进行模式匹配,匹配过了以后去执行这个URL注册的时候的block,一般是打开某个VC。

2.3 匹配函数

第三个核心函数是_routeURL:withParameters:executeRouteBlock:。这个函数比前面的核心函数长一点。这个函数分五个部分。其中引入了JLRRouteRequestJLRRouteResponse这两个对象。(这里为了方便看代码,我把所有的注释都删了)

- (BOOL)_routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters executeRouteBlock:(BOOL)executeRouteBlock
{
    ...
    
    BOOL didRoute = NO;
    #1
    JLRRouteRequest *request = [[JLRRouteRequest alloc] initWithURL:URL alwaysTreatsHostAsPathComponent:alwaysTreatsHostAsPathComponent];
    
    #2
    for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
        
        JLRRouteResponse *response = [route routeResponseForRequest:request decodePlusSymbols:shouldDecodePlusSymbols];
        if (!response.isMatch) {
            continue;
        }
        
        [self _verboseLog:@"Successfully matched %@", route];
        
        if (!executeRouteBlock) {
            return YES;
        }
        
        #3
        NSMutableDictionary *finalParameters = [NSMutableDictionary dictionary];
        [finalParameters addEntriesFromDictionary:response.parameters];
        [finalParameters addEntriesFromDictionary:parameters];
        [self _verboseLog:@"Final parameters are %@", finalParameters];
        
        didRoute = [route callHandlerBlockWithParameters:finalParameters];
        
        if (didRoute) {
            break;
        }
    }
    
    if (!didRoute) {
        [self _verboseLog:@"Could not find a matching route"];
    }
    
    #4
    if (!didRoute && self.shouldFallbackToGlobalRoutes && ![self _isGlobalRoutesController]) {
        [self _verboseLog:@"Falling back to global routes..."];
        didRoute = [[JLRoutes globalRoutes] _routeURL:URL withParameters:parameters executeRouteBlock:executeRouteBlock];
    }
    
    #5
    if (!didRoute && executeRouteBlock && self.unmatchedURLHandler) {
        [self _verboseLog:@"Falling back to the unmatched URL handler"];
        self.unmatchedURLHandler(self, URL, parameters);
    }
    
    return didRoute;
}

2.3.1 第一部分

第一部分就是创建一个JLRRouteRequest对象,对象包含三个参数:URLpathComponentsqueryParamsinitWithURL:函数其实就是根据传入的URL解析成上面几个参数。我觉得这个函数大家可以自己看看,我就不贴代码了,代码中用到了NSURLComponents相关的参数,我觉得可以用张图片介绍一波,这样大家看这个init函数应该方便些。

NSURLComponents.png

2.3.2 第二部分

第二部分就是URL匹配的部分啦,也是代码的核心(存储+匹配)。这里我们遍历每一个self.mutableRoutes,取出URLPattern,和刚刚生成的JLRRouteRequest进行匹配,匹配结果封装在JLRRouteResponse中。

其实匹配的过程不是很难,for循环遍历self.patternComponents。即代码for (NSString *patternComponent in self.patternComponents),同时取URLComponent即代码URLComponent = request.pathComponents[index];针对每次取出来的一部分进行匹配:包括是否有:作为前缀的匹配,是否有*作为前缀的匹配,已经是否相等。

PS:在URL中:应该是变量的意思,*不是很懂,希望大神解释。

- (JLRRouteResponse *)routeResponseForRequest:(JLRRouteRequest *)request decodePlusSymbols:(BOOL)decodePlusSymbols
{
    ...
    
    JLRRouteResponse *response = [JLRRouteResponse invalidMatchResponse];
    NSMutableDictionary *routeParams = [NSMutableDictionary dictionary];
    BOOL isMatch = YES;
    NSUInteger index = 0;
    
    for (NSString *patternComponent in self.patternComponents) {
        NSString *URLComponent = nil;
        
        // figure out which URLComponent it is, taking wildcards into account
        if (index < [request.pathComponents count]) {
            URLComponent = request.pathComponents[index];
        } else if ([patternComponent isEqualToString:@"*"]) {
            // match /foo by /foo/*
            URLComponent = [request.pathComponents lastObject];
        }
        
        if ([patternComponent hasPrefix:@":"]) {
            // this is a variable, set it in the params
            NSString *variableName = [self variableNameForValue:patternComponent];
            NSString *variableValue = [self variableValueForValue:URLComponent decodePlusSymbols:decodePlusSymbols];
            routeParams[variableName] = variableValue;
        } else if ([patternComponent isEqualToString:@"*"]) {
            // match wildcards
            NSUInteger minRequiredParams = index;
            if (request.pathComponents.count >= minRequiredParams) {
                // match: /a/b/c/* has to be matched by at least /a/b/c
                routeParams[JLRouteWildcardComponentsKey] = [request.pathComponents subarrayWithRange:NSMakeRange(index, request.pathComponents.count - index)];
                isMatch = YES;
            } else {
                // not a match: /a/b/c/* cannot be matched by URL /a/b/
                isMatch = NO;
            }
            break;
        } else if (![patternComponent isEqualToString:URLComponent]) {
            // break if this is a static component and it isn't a match
            isMatch = NO;
            break;
        }
        index++;
    }
    
    if (isMatch) {
        // if it's a match, set up the param dictionary and create a valid match response
        NSMutableDictionary *params = [NSMutableDictionary dictionary];
        [params addEntriesFromDictionary:[JLRParsingUtilities queryParams:request.queryParams decodePlusSymbols:decodePlusSymbols]];
        [params addEntriesFromDictionary:routeParams];
        [params addEntriesFromDictionary:[self baseMatchParametersForRequest:request]];
        response = [JLRRouteResponse validMatchResponseWithParameters:[params copy]];
    }
    
    return response;
}

其实从这个for循环我们就能窥探出,这个框架匹配效率不是很高,因为当URLPattern变多的时候,遍历一遍耗时比较多。

2.3.3 第三、四、五部分

第三部分是将response的参数存入finalParameters字典中,然后执行routehandlerBlock

第四部分是用globalRoutes去匹配,第五部分是什么都匹配不了,调用unmatchedURLHandler

3 优劣势分析

优势:

  1. URL匹配,通俗易懂。

劣势:

  1. URL增多的话,匹配效率比较低。
  2. URL不太灵活,如果需要传UIImage之类的,需要用字典,加入直接将image的参数放入路由的URL中,可能会解析错误。
  3. 可扩展性差,表的维护很重要。

4 致谢

本文参考了github和一些博客:

  1. https://github.com/joeldev/JLRoutes
  2. https://github.com/lizhaojie/JLRoutesFirstDemo
  3. http://www.jianshu.com/p/55393770805a

相关文章

  • 组件化初探 - JLRoutes源码分析

    最近在学习组件化的知识,看到了JLRoutes这个框架,这是一个基于路由匹配的框架,我大致读了这个框架的源码。下面...

  • iOS组件化

    1.JLRoutes源码分析2.iOS 组件化时代到临3.蘑菇街 App 的组件化之路4.CTMediator

  • iOS JLRoutes 路由组件化

    1.添加路由组件化 JLRoutes 2.引入JLRoutes 头文件 import

  • iOS 组件化-JLRoutes

    一、JLRoutes原理 运行机制是:保存一个全局的map(routeControllersMap),map中的k...

  • JLRoutes——小人物上篮

    看完后 HHRoute源码分析后,味道更佳,更易消化。 JLRoutes支持scheme,HHRoute没有 JL...

  • 『ios』JLRoutes源码分析

    阅读源码是目前沉淀自己的最好方式~ route 都是知道什么意思,路由的意思,如果没接触vue之前我对路由也只是了...

  • 对vue组件化的理解

    组件化定义、优点、使用场景和注意事项等方面展开陈述,同时要强调vue中组件化的一些特点。 源码分析1:组件定义 组...

  • Android组件化:我们到底该怎样学习和运用组件化?

    前言 上个星期,我分享了一篇关于Android组件化的文章↓↓↓Android组件化初探【含Demo】[https...

  • iOS组件化使用JLRoutes示例

    前言:看到很多人写的文章几乎都是不同APP跳转,并没有关于组件化使用的示例。应用内跳转对于初学的人也找不到方向,所...

  • 组件化初探

    一、创建本地组件化 首先创建一个存储组件化的文件夹:例如 cd到这个文件夹中,使用下边命令创建本地组件库(注:我在...

网友评论

    本文标题:组件化初探 - JLRoutes源码分析

    本文链接:https://www.haomeiwen.com/subject/huizmxtx.html