源码解读

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

2018-02-05  本文已影响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 。同时也引入了 * 或者 (/可选路径) 匹配规则 和 优先级设置。由于整体工程较小,说明下每个类的功能(按照从最基础的开始枚举)。

     /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
     
     */

类功能说明

JLRParsingUtilities

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

+ (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];
}

该部分逻辑比较简单:

JLRoutes

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

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

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


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

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

上一篇下一篇

猜你喜欢

热点阅读