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,极大的提高了匹配的容错率。

代码实例

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容