最近在学习组件化的知识,看到了JLRoutes这个框架,这是一个基于路由匹配的框架,我大致读了这个框架的源码。下面就把我读的新的和大家分享一下。主要分三个部分,第一部分介绍框架结构,第二部分介绍核心代码,第三部分分析优劣。
1 框架结构
JLRoutes主要是几个文件:JLRoutes
,JLRRouteDefinition
,Utils
,JLRRouteRequest
,JLRRouteResponse
。
最近在halfrost的博客中看到他整理的一个数据结构,觉得非常好,对于讲解一个框架类与类之间的关于很有帮助,这里贴我也出来,大家一定会一目了然。
JLRoutes_数据存储.jpeg2 核心代码
2.1 routesForScheme函数
第一个核心函数是routesForScheme
函数,这个函数是根据scheme
找对应的routes
。routeControllersMap
是一个字典,key
是scheme
,也就是命名空间;value
是一个JLRoutes
实例,这个JLRoutes
有个成员变量叫mutableRoutes
,存放所有的这个命名空间下的routes
。
+ (instancetype)routesForScheme:(NSString *)scheme
{
JLRoutes *routesController = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
routeControllersMap = [[NSMutableDictionary alloc] init];
});
if (!routeControllersMap[scheme]) {
routesController = [[self alloc] init];
routesController.scheme = scheme;
routeControllersMap[scheme] = routesController;
}
routesController = routeControllersMap[scheme];
return routesController;
}
2.2 addRoute函数
第二个核心函数就是addRoute
了。函数分四部分,第一部分是处理可变的参数,第二部分是创建一个自定义的实例JLRRouteDefinition
,第三部分处理一下可变参数的路由注册问题(遍历所有可能注册),第四部分注册路由。这四部分在接下来都会分开讲。
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary<NSString *, id> *parameters))handlerBlock
{
#1
NSArray <NSString *> *optionalRoutePatterns = [JLRParsingUtilities expandOptionalRoutePatternsForPattern:routePattern];
#2
JLRRouteDefinition *route = [[JLRRouteDefinition alloc] initWithScheme:self.scheme pattern:routePattern priority:priority handlerBlock:handlerBlock];
#3
if (optionalRoutePatterns.count > 0) {
// there are optional params, parse and add them
for (NSString *pattern in optionalRoutePatterns) {
[self _verboseLog:@"Automatically created optional route: %@", route];
JLRRouteDefinition *optionalRoute = [[JLRRouteDefinition alloc] initWithScheme:self.scheme pattern:pattern priority:priority handlerBlock:handlerBlock];
[self _registerRoute:optionalRoute];
}
return;
}
#4
[self _registerRoute:route];
}
2.2.1 第一部分
先讲第一个部分,第一个函数大概的意思就是:如果来了一种带括号的url:/path/:thing/(/a)(/b)(/c)
,我们把它拆分组合成如下一个array。
/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
- 第一个函数用的
NSScanner
,其中有个比较关键的方法就是scanUpToString:intoString:
,截取到指定字符串用的,把参数都截取下来。 - 第二个函数显而易见就是组合,将()里可能出现的情况进行排列组合。
- 这个函数我的理解是URL中存在可选参数,我们这里进行排列组合,存放起来,后期来什么URL到这个数组里进行匹配就行了。
+ (NSArray <NSString *> *)expandOptionalRoutePatternsForPattern:(NSString *)routePattern
{
routePattern = @"/push/:controller/(/a)(/b)(/c)";
if ([routePattern rangeOfString:@"("].location == NSNotFound) {
return @[];
}
NSString *baseRoute = nil;
NSArray *components = [self _optionalComponentsForPattern:routePattern baseRoute:&baseRoute];
NSArray *routes = [self _routesForOptionalComponents:components baseRoute:baseRoute];
return routes;
}
一个函数都把一个文件讲完了😀
2.2.2 第二部分
第二个函数很简单,初始化了一个用来存放route
的实例,实例里有scheme
命名空间,pattern
模式,priority
优先级,和要执行的handlerBlock
。
- (instancetype)initWithScheme:(NSString *)scheme pattern:(NSString *)pattern priority:(NSUInteger)priority handlerBlock:(BOOL (^)(NSDictionary *parameters))handlerBlock
{
if ((self = [super init])) {
self.scheme = scheme;
self.pattern = pattern;
self.priority = priority;
self.handlerBlock = handlerBlock;
if ([pattern characterAtIndex:0] == '/') {
pattern = [pattern substringFromIndex:1];
}
self.patternComponents = [pattern componentsSeparatedByString:@"/"];
}
return self;
}
2.2.3 第三部分
略
2.2.4 第四部分
第四部分是路由注册,这个函数很简单,如果在某个scheme
命名空间下,如果从没有注册过,addObject:
之,如果有路由存在,遍历一下,根据优先级插入。
- (void)_registerRoute:(JLRRouteDefinition *)route
{
if (route.priority == 0 || self.mutableRoutes.count == 0) {
[self.mutableRoutes addObject:route];
} else {
NSUInteger index = 0;
BOOL addedRoute = NO;
// search through existing routes looking for a lower priority route than this one
for (JLRRouteDefinition *existingRoute in [self.mutableRoutes copy]) {
if (existingRoute.priority < route.priority) {
// if found, add the route after it
[self.mutableRoutes 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.mutableRoutes addObject:route];
}
}
}
这里总结一下,一个scheme
对应一个JLRoute
实例,我们可以为这个JLRoute
注册很多中URLPattern
,我们现在如果需要openURL
,那么会进行模式匹配,匹配过了以后去执行这个URL注册的时候的block
,一般是打开某个VC。
2.3 匹配函数
第三个核心函数是_routeURL:withParameters:executeRouteBlock:
。这个函数比前面的核心函数长一点。这个函数分五个部分。其中引入了JLRRouteRequest
和JLRRouteResponse
这两个对象。(这里为了方便看代码,我把所有的注释都删了)
- (BOOL)_routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters executeRouteBlock:(BOOL)executeRouteBlock
{
...
BOOL didRoute = NO;
#1
JLRRouteRequest *request = [[JLRRouteRequest alloc] initWithURL:URL alwaysTreatsHostAsPathComponent:alwaysTreatsHostAsPathComponent];
#2
for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
JLRRouteResponse *response = [route routeResponseForRequest:request decodePlusSymbols:shouldDecodePlusSymbols];
if (!response.isMatch) {
continue;
}
[self _verboseLog:@"Successfully matched %@", route];
if (!executeRouteBlock) {
return YES;
}
#3
NSMutableDictionary *finalParameters = [NSMutableDictionary dictionary];
[finalParameters addEntriesFromDictionary:response.parameters];
[finalParameters addEntriesFromDictionary:parameters];
[self _verboseLog:@"Final parameters are %@", finalParameters];
didRoute = [route callHandlerBlockWithParameters:finalParameters];
if (didRoute) {
break;
}
}
if (!didRoute) {
[self _verboseLog:@"Could not find a matching route"];
}
#4
if (!didRoute && self.shouldFallbackToGlobalRoutes && ![self _isGlobalRoutesController]) {
[self _verboseLog:@"Falling back to global routes..."];
didRoute = [[JLRoutes globalRoutes] _routeURL:URL withParameters:parameters executeRouteBlock:executeRouteBlock];
}
#5
if (!didRoute && executeRouteBlock && self.unmatchedURLHandler) {
[self _verboseLog:@"Falling back to the unmatched URL handler"];
self.unmatchedURLHandler(self, URL, parameters);
}
return didRoute;
}
2.3.1 第一部分
第一部分就是创建一个JLRRouteRequest
对象,对象包含三个参数:URL
,pathComponents
,queryParams
。initWithURL:
函数其实就是根据传入的URL解析成上面几个参数。我觉得这个函数大家可以自己看看,我就不贴代码了,代码中用到了NSURLComponents
相关的参数,我觉得可以用张图片介绍一波,这样大家看这个init
函数应该方便些。
2.3.2 第二部分
第二部分就是URL匹配的部分啦,也是代码的核心(存储+匹配)。这里我们遍历每一个self.mutableRoutes
,取出URLPattern
,和刚刚生成的JLRRouteRequest
进行匹配,匹配结果封装在JLRRouteResponse
中。
其实匹配的过程不是很难,for
循环遍历self.patternComponents
。即代码for (NSString *patternComponent in self.patternComponents)
,同时取URLComponent
即代码URLComponent = request.pathComponents[index];
针对每次取出来的一部分进行匹配:包括是否有:
作为前缀的匹配,是否有*
作为前缀的匹配,已经是否相等。
PS:在URL中:
应该是变量的意思,*
不是很懂,希望大神解释。
- (JLRRouteResponse *)routeResponseForRequest:(JLRRouteRequest *)request decodePlusSymbols:(BOOL)decodePlusSymbols
{
...
JLRRouteResponse *response = [JLRRouteResponse invalidMatchResponse];
NSMutableDictionary *routeParams = [NSMutableDictionary dictionary];
BOOL isMatch = YES;
NSUInteger index = 0;
for (NSString *patternComponent in self.patternComponents) {
NSString *URLComponent = nil;
// figure out which URLComponent it is, taking wildcards into account
if (index < [request.pathComponents count]) {
URLComponent = request.pathComponents[index];
} else if ([patternComponent isEqualToString:@"*"]) {
// match /foo by /foo/*
URLComponent = [request.pathComponents lastObject];
}
if ([patternComponent hasPrefix:@":"]) {
// this is a variable, set it in the params
NSString *variableName = [self variableNameForValue:patternComponent];
NSString *variableValue = [self variableValueForValue:URLComponent decodePlusSymbols:decodePlusSymbols];
routeParams[variableName] = variableValue;
} else if ([patternComponent isEqualToString:@"*"]) {
// match wildcards
NSUInteger minRequiredParams = index;
if (request.pathComponents.count >= minRequiredParams) {
// match: /a/b/c/* has to be matched by at least /a/b/c
routeParams[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) {
// if it's a match, set up the param dictionary and create a valid match response
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params addEntriesFromDictionary:[JLRParsingUtilities queryParams:request.queryParams decodePlusSymbols:decodePlusSymbols]];
[params addEntriesFromDictionary:routeParams];
[params addEntriesFromDictionary:[self baseMatchParametersForRequest:request]];
response = [JLRRouteResponse validMatchResponseWithParameters:[params copy]];
}
return response;
}
其实从这个for循环我们就能窥探出,这个框架匹配效率不是很高,因为当URLPattern变多的时候,遍历一遍耗时比较多。
2.3.3 第三、四、五部分
第三部分是将response
的参数存入finalParameters
字典中,然后执行route
的handlerBlock
。
第四部分是用globalRoutes
去匹配,第五部分是什么都匹配不了,调用unmatchedURLHandler
。
3 优劣势分析
优势:
- URL匹配,通俗易懂。
劣势:
- URL增多的话,匹配效率比较低。
- URL不太灵活,如果需要传UIImage之类的,需要用字典,加入直接将image的参数放入路由的URL中,可能会解析错误。
- 可扩展性差,表的维护很重要。
4 致谢
本文参考了github和一些博客:
网友评论