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

## 引子 近期要开新项目,包括iOS&Android。正好是做一款强运营的电商类APP。所以无论如何都是要用到Router的。 参考github上的Router开源库,整体看过来基本JLRoutes用的最多,今天就来掰扯掰扯JLRoutes的实现([JLRoutes 2.1链接](https://github.com/joeldev/JLRoutes/tree/2.1))。 — ### 组件化思路 先简单说下常用组件化思想和背景,在大公司或者复杂的项目中,常见的方式是需要跳转到某个具体的viewController的时候,`#import viewController` 然后进行push或者present进行展示。这样会引入两个问题: - 模块不清晰,文件之间相互依赖 - 维护不方便,假如依赖的模块进行修改时,其他跳转的部分都需要同步修改 为了让工程结构清晰、易读和降低维护成本,常用的会进行模块拆分,通过 `Router` 的概念进行依赖相互之间的跳转。所以可以看到 `JLRouter` 的Star会很高。 ### JLRoutes组件说明 `JLRoutes` 其实只做了一件事情,就是管理对应的
     /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 ### 类功能说明 ##### 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的原理上是一致的。 — 可以关注个人公众号联系我,欢迎大家一起沟通交流。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值