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