美文网首页源码解读
【iOS开源库】JLRoutes源码阅读&原理解析

【iOS开源库】JLRoutes源码阅读&原理解析

作者: 库莫 | 来源:发表于2018-02-05 13:20 被阅读0次

    引子

    近期要开新项目,包括iOS&Android。正好是做一款强运营的电商类APP。所以无论如何都是要用到Router的。
    </br>
    参考github上的Router开源库,整体看过来基本JLRoutes用的最多,今天就来掰扯掰扯JLRoutes的实现(JLRoutes 2.1链接)。

    组件化思路

    先简单说下常用组件化思想和背景,在大公司或者复杂的项目中,常见的方式是需要跳转到某个具体的viewController的时候,#import viewController 然后进行push或者present进行展示。这样会引入两个问题:

    • 模块不清晰,文件之间相互依赖
    • 维护不方便,假如依赖的模块进行修改时,其他跳转的部分都需要同步修改

    为了让工程结构清晰、易读和降低维护成本,常用的会进行模块拆分,通过 Router 的概念进行依赖相互之间的跳转。所以可以看到 JLRouter 的Star会很高。

    JLRoutes组件说明

    JLRoutes 其实只做了一件事情,就是管理对应的 < 路径Block >的映射和管理。然后根据传入的 URI 进行查询和执行对应的 Block 。同时也引入了 * 或者 (/可选路径) 匹配规则 和 优先级设置。由于整体工程较小,说明下每个类的功能(按照从最基础的开始枚举)。

    • JLRParsingUtilities 管理可选路径枚举,下面举个例子就知道了,括号为可选(源码标注中有一处错误,下列描述已经修改):
         /path/:thing/(/a)(/b)(/c)
         
         create the following paths:
         
         /path/:thing/a/b/c
         /path/:thing/a/b
         /path/:thing/a/c
         /path/:thing/b/c
         /path/:thing/a
         /path/:thing/b
         /path/:thing/c
         
         */
    
    • JLRRouteRequest 提供输入URL的分解,分解为scheme、path、param和fragment等
    • JLRRouteResponse 则是结果的封装,包括匹配的参数、是否匹配等内容
    • JLRRouteDefinition 用固有的规则初始化,去计算 JLRouteRequest 是否匹配自己的规则并输出
    • JLRoutes 进行Route的管理、调度、优先级管理,核心是 NSArray<JLRRouteDefinition*> 部分
    • JLRRouteHandler 工具辅助类,不参与主逻辑,可以忽略

    类功能说明

    JLRParsingUtilities

    JLRParsingUtilities 主要提供根据传入的匹配链接生成对应的合规的URI。主要的功能可以查看代码中给的说明:例如路径 /path/:thing/(/a)(/b)(/c) 中包含有可选路径 a b c ,该类主要是提供几个功能模块:

    • 展开可选路径,生成list。主要是如下实现:
    + (NSArray <NSString *> *)expandOptionalRoutePatternsForPattern:(NSString *)routePattern
    {
        /* this method exists to take a route pattern that is known to contain optional params, such as:
         
         /path/:thing/(/a)(/b)(/c)
         
         and create the following paths:
         
         /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
         
         */
        
        if ([routePattern rangeOfString:@"("].location == NSNotFound) {
            return @[];
        }
        
        // First, parse the route pattern into subpath objects.
        NSArray <JLRParsingUtilities_RouteSubpath *> *subpaths = [self _routeSubpathsForPattern:routePattern];
        if (subpaths.count == 0) {
            return @[];
        }
        
        // Next, etract out the required subpaths.
        NSSet <JLRParsingUtilities_RouteSubpath *> *requiredSubpaths = [NSSet setWithArray:[subpaths JLRoutes_filter:^BOOL(JLRParsingUtilities_RouteSubpath *subpath) {
            return !subpath.isOptionalSubpath;
        }]];
        
        // Then, expand the subpath permutations into possible route patterns.
        NSArray <NSArray <JLRParsingUtilities_RouteSubpath *> *> *allSubpathCombinations = [subpaths JLRoutes_allOrderedCombinations];
        
        // Finally, we need to filter out any possible route patterns that don't actually satisfy the rules of the route.
        // What this means in practice is throwing out any that do not contain all required subpaths (since those are explicitly not optional).
        NSArray <NSArray <JLRParsingUtilities_RouteSubpath *> *> *validSubpathCombinations = [allSubpathCombinations JLRoutes_filter:^BOOL(NSArray <JLRParsingUtilities_RouteSubpath *> *possibleRouteSubpaths) {
            return [requiredSubpaths isSubsetOfSet:[NSSet setWithArray:possibleRouteSubpaths]];
        }];
        
        // Once we have a filtered list of valid subpaths, we just need to convert them back into string routes that can we registered.
        NSArray <NSString *> *validSubpathRouteStrings = [validSubpathCombinations JLRoutes_map:^id(NSArray <JLRParsingUtilities_RouteSubpath *> *subpaths) {
            NSString *routePattern = @"/";
            for (JLRParsingUtilities_RouteSubpath *subpath in subpaths) {
                NSString *subpathString = [subpath.subpathComponents componentsJoinedByString:@"/"];
                routePattern = [routePattern stringByAppendingPathComponent:subpathString];
            }
            return routePattern;
        }];
        
        // Before returning, sort them by length so that the longest and most specific routes are registered first before the less specific shorter ones.
        validSubpathRouteStrings = [validSubpathRouteStrings sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"length" ascending:NO selector:@selector(compare:)]]];
        
        return validSubpathRouteStrings;
    }
    
    

    这里代码首先根据 [self _routeSubpathsForPattern:routePattern] 获取分段Array列表,然后通过fliter 筛选出可选项。
    然后通过 NSArray的JLRoutes_Utilities 扩展,生成对应组合的路径。例如通过 @[@"a", @"b", @"c"] 生成如下list:

    \a\b\c
    \a\b
    \a\c
    \b\c
    \a
    \b
    \c
    \
    

    然后通过过滤部分前缀为空产生的不符合前缀的string,进行排序返回。

    JLRRouteRequest & JLRRouteResponse

    其实这两个类看名字就知道,主要是提供在有规则的情况下,我们输入一个 URL,例如: http://www.baidu.com/a/b/c 会生成一个 JLRRouteRequest 对象,将协议、域名和后面的路径、参数等都拆分开来。具体的可以参考源码,逻辑比较简单。
    JLRRouteResponse 则是在经过规则匹配后,得到的相关参数的一个返回对象。

    JLRRouteDefinition

    JLRRouteDefinition 是匹配规则的核心类,对应的是输入一个匹配规则,例如:weixin://push/:viewController/:param1/:param2 ,那么先解析需要的参数和协议、前缀、优先级以及callback,得到对应的信息后,暴露匹配接口,这里就用到了上面提到的 JLRRouteRequest 对象。

    - (NSDictionary <NSString *, NSString *> *)routeVariablesForRequest:(JLRRouteRequest *)request
    {
        NSMutableDictionary *routeVariables = [NSMutableDictionary dictionary];
        
        BOOL isMatch = YES;
        NSUInteger index = 0;
        
        for (NSString *patternComponent in self.patternPathComponents) {
            NSString *URLComponent = nil;
            BOOL isPatternComponentWildcard = [patternComponent isEqualToString:@"*"];
            
            if (index < [request.pathComponents count]) {
                URLComponent = request.pathComponents[index];
            } else if (!isPatternComponentWildcard) {
                // URLComponent is not a wildcard and index is >= request.pathComponents.count, so bail
                isMatch = NO;
                break;
            }
            
            if ([patternComponent hasPrefix:@":"]) {
                // this is a variable, set it in the params
                NSAssert(URLComponent != nil, @"URLComponent cannot be nil");
                NSString *variableName = [self routeVariableNameForValue:patternComponent];
                NSString *variableValue = [self routeVariableValueForValue:URLComponent];
                
                // Consult the parsing utilities as well to do any other standard variable transformations
                BOOL decodePlusSymbols = ((request.options & JLRRouteRequestOptionDecodePlusSymbols) == JLRRouteRequestOptionDecodePlusSymbols);
                variableValue = [JLRParsingUtilities variableValueFrom:variableValue decodePlusSymbols:decodePlusSymbols];
                
                routeVariables[variableName] = variableValue;
            } else if (isPatternComponentWildcard) {
                // match wildcards
                NSUInteger minRequiredParams = index;
                if (request.pathComponents.count >= minRequiredParams) {
                    // match: /a/b/c/* has to be matched by at least /a/b/c
                    routeVariables[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) {
            // Return nil to indicate that there was not a match
            routeVariables = nil;
        }
        
        return [routeVariables copy];
    }
    

    该部分逻辑比较简单:

    • 判断是否是参数或者有通配符,如果没有,那么现在就判断路径是否相等
    • 如果匹配参数,那么记录key-value对
    • 如果有通配符,主要是匹配后面的参数数量等
      得到参数后,返回到 JLRoutes 模块进行 JLRRouteResponse 对象封装出去

    JLRoutes

    JLRoutes 提供对外的接口,以及Routes的管理。
    主要有几个层级:

    • Scheme,可以默认使用global的,也可以添加自定义的,方便APP扩展不同scheme
    • 管理规则对象JLRRouteDefinition , 根据得到的一系列 JLRRouteDefinition 对象,在传入路径时,根据优先级遍历去判断是否符合当前规则,如果符合则进入相关的callback。

    整体逻辑如下,由于JLRoutes 主要是提供路由功能,所以功能模块都是偏向于对URI进行解析和分解。所以匹配到的具体逻辑操作,都是依赖给定的callback去进行处理。

    整个的Routes模块做的事情比较简单,也可以了解下大公司做的组件化方案,他们可能实现方式上会不同,但用的Routes的原理上是一致的。


    可以关注个人公众号联系我,欢迎大家一起沟通交流。

    可以关注个人公众号联系我,欢迎大家一起沟通交流。

    相关文章

      网友评论

        本文标题:【iOS开源库】JLRoutes源码阅读&原理解析

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