2020-04-23

JLRoutes的另类使用及解析

一、简介

JLRoutes是一个基于块的API的URL路由库。 它旨在使您以最少的代码轻松处理应用程序中的复杂URL方案。

通过URL schemes可以实现APP内部,Web和APP之间,以及APP和APP之间页面的跳转。

二、原理

JLRoutes是通过解析URL不同的参数,并用block回调的方式处理页面间的传值以及跳转。其本质就是在程序中注册一个全局的字典,key是URL scheme,value是一个参数为字典的block回调。

三、使用

在我自己的项目中使用的JLRoutes是比较老的版本,可能是1.6版本左右,功能比较简单,只用JLRoutes这一个类。在使用的过程中为了更加方便因此把routeURL:的返回改为id (之前为BOOL),因此在每个路由block里返回任意非nil对象,表示不再继续走下一个路由 (之前返回YES则不再往下走),这就是本文的另类之处。

/// Routes a URL, calling handler blocks (for patterns that match URL) until one returns YES, optionally specifying add'l parameters
+ (id)routeURL:(NSURL *)URL;
+ (id)routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters;

- (id)routeURL:(NSURL *)URL; // instance method
- (id)routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters; // instance method

做一个简单的跳转场景:A页面跳转到B页面
首先在B页面的load方法中使用addRoute:handler方法注册B页面的路由

+ (void)load
{
    [JLRoutes addRoute:TZ_SCENESA handler:^id(NSDictionary *parameters) {
        TZViewControllerA *vc = [[TZViewControllerA alloc] init];
        return vc;
    }];
}

其中TZ_SCENESA为B页面的路由宏
注册完以后,会将注册的路由存放在一个全局字典中,只需要在跳转的过程调用对应页面的路由即可。
为了方便我简单的封装了一个工具类TZRouter,来处理路由的跳转

[[TZRouter sharedInstance] openURL:TZ_SCENESA params:nil];

其中TZ_SCENESA为B页面的路由地址,params为这个页面是所需要传递的参数
这样一个简单的跳转就实现了。

四、源码分析

4.1 路由注册

- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(id (^)(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 = [^id (NSDictionary *params) {
            return [NSNumber numberWithBool: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];
    }
}

routePattern:路由
priority:优先级
handlerBlock:要处理的回调方法
该方法生成一个_JLRoute类型的实例对象,改对象记录了注册页面的路由、优先级、回调。并根据优先级将路由添加到全局数组中去。

4.2 路由解析

- (NSDictionary *)parametersForURL:(NSURL *)URL components:(NSArray *)URLComponents {
    NSDictionary *routeParameters = nil;
    
    if (!self.patternPathComponents) {
        self.patternPathComponents = [[self.pattern pathComponents] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT SELF like '/'"]];
    }
    // do a quick component count check to quickly eliminate incorrect patterns
    BOOL componentCountEqual = self.patternPathComponents.count == URLComponents.count;
    BOOL routeContainsWildcard = !NSEqualRanges([self.pattern rangeOfString:@"*"], NSMakeRange(NSNotFound, 0));
    if (componentCountEqual || routeContainsWildcard) {
        // now that we've identified a possible match, move component by component to check if it's a match
        NSUInteger componentIndex = 0;
        NSMutableDictionary *variables = [NSMutableDictionary dictionary];
        BOOL isMatch = YES;
        
        for (NSString *patternComponent in self.patternPathComponents) {
            NSString *URLComponent = nil;
            if (componentIndex < [URLComponents count]) {
                URLComponent = URLComponents[componentIndex];
            } else if ([patternComponent isEqualToString:@"*"]) { // match /foo by /foo/*
                URLComponent = [URLComponents lastObject];
            }
            
            if ([patternComponent hasPrefix:@":"]) {
                // this component is a variable
                NSString *variableName = [patternComponent substringFromIndex:1];
                NSString *variableValue = URLComponent;
                NSString *urlDecodedVariableValue = [variableValue JLRoutes_URLDecodedString];
                if ([variableName length] > 0 && [urlDecodedVariableValue length] > 0) {
                    variables[variableName] = urlDecodedVariableValue;
                }
            } else if ([patternComponent isEqualToString:@"*"]) {
                // match wildcards
                variables[kJLRouteWildcardComponentsKey] = [URLComponents subarrayWithRange:NSMakeRange(componentIndex, URLComponents.count-componentIndex)];
                isMatch = YES;
                break;
            } else if (![patternComponent isEqualToString:URLComponent]) {
                // a non-variable component did not match, so this route doesn't match up - on to the next one
                isMatch = NO;
                break;
            }
            componentIndex++;
        }
        if (isMatch) {
            routeParameters = variables;
        }
    }
    return routeParameters;
}

该方法通过解析路由和全局数组中的路由进行匹配,找到要跳转页面的路由,从而建立跳转关系

五、版本比对

在JLRoutes最新的2.1版本中,JLRoutes由以前的一个JLRoutes的基础上增加了JLRParsingUtilities、JLRRouteDefinition、JLRRouteHandler、JLRRouteRequest、JLRRouteResponse这五个类。

JLRoutes:作为JLRoutes框架的入口,负责注册URL,管理路由以及分配路由。
    
JLRRouteDefinition:用来封装注册URL的路由信息,包括URL scheme,route pattern,和priority,并且可以根据request提供相应的response。可以通过继承该类来实现自定义的匹配方式。
    
JLRRouteRequest:用来封装一个URL的路由请求信息,包括URL、解析后的path components 和 query parameters。

JLRRouteResponse:根据URL匹配路由信息时的response,包含isMatch、parameters 等信息。如果 JLRRouteDefinition匹配URL成功时,就会设置属性isMatch为YES,同时将解析URL后的参数和默认参数、附加参数组合返回

JLRRouteHandler:自定义路由handler,将回调参数处理的逻辑交给自定义类去处理。

JLRParsingUtilities:解析URL参数的工具类。

其中在路由解析部分使用NSURLComponents和NSScanner,极大的提高了匹配的容错率。

代码实例

你可能感兴趣的:(2020-04-23)