JLRoutes在GitHub上有4.2k星。它是一个具有简单的基于块的API的URL路由库。 旨在让开发者能够使用较少的代码轻松处理应用程序中的复杂URL scheme。
这篇文章主要分以下三个部分:
- 方法注释
- 方法调用流程图
- 主要内部实现
方法注释
- JLRoutes
JLRoutes类是此框架的主要入口点。用于访问schemes(方案, 应用的标识),管理路由和路由URL。
// 控制这个路由器如果在当前命名空间中不能匹配,是否尝试将URL与全局路由匹配。默认为NO。
@property (nonatomic, assign) BOOL shouldFallbackToGlobalRoutes;
// 任何时候调用routeURL返回NO的回调。与shouldFallbackToGlobalRoutes属性相关
@property (nonatomic, copy, nullable) void (^unmatchedURLHandler)(JLRoutes *routes, NSURL *__nullable URL, NSDictionary<NSString *, id> *__nullable parameters);
// 返回一个具有全局路由scheme的路由实例
+ (instancetype)globalRoutes;
// 返回一个具有指定scheme的路由实例
+ (instancetype)routesForScheme:(NSString *)scheme;
// 注销并删除指定scheme的路由scheme
+ (void)unregisterRouteScheme:(NSString *)scheme;
// 注销所有路由scheme
+ (void)unregisterAllRouteSchemes;
// JLRRouteDefinition为路由模型对象。通过直接插入路由模型对象来添加路由。这可能是JLRRouteDefinition的子类来提供自定义的路由逻辑
- (void)addRoute:(JLRRouteDefinition *)routeDefinition;
// 在接收scheme中注册一个默认优先级(0)的routePattern
- (void)addRoute:(NSString *)routePattern handler:(BOOL (^__nullable)(NSDictionary<NSString *, id> *parameters))handlerBlock;
//在全局scheme命名空间里注册一个routePattern,当该路由模式与URL匹配时回调handlerBlock。该block的返回值是一个BOOL值,表示handlerBlock是否实际处理了路由。如果返回NO,JLRoutes将继续尝试寻找匹配路由。
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^__nullable)(NSDictionary<NSString *, id> *parameters))handlerBlock;
// 在接收scheme中给一个handler注册多个具有默认优先级(0)的路由模式
- (void)addRoutes:(NSArray<NSString *> *)routePatterns handler:(BOOL (^__nullable)(NSDictionary<NSString *, id> *parameters))handlerBlock;
// 从接收scheme中移除路由
- (void)removeRoute:(JLRRouteDefinition *)routeDefinition;
// 从接收scheme中移除第一个匹配此路由模式的路由
- (void)removeRouteWithPattern:(NSString *)routePattern;
// 从接收scheme中移除所有路由
- (void)removeAllRoutes;
// 使用字典风格的下标注册一个具有默认优先级(0)的路由模式
- (void)setObject:(nullable id)handlerBlock forKeyedSubscript:(NSString *)routePatten;
// 返回在接收scheme中的所有已注册路由
- (NSArray <JLRRouteDefinition *> *)routes;
// 返回所有键是scheme对应的已注册路由
+ (NSDictionary <NSString *, NSArray <JLRRouteDefinition *> *> *)allRoutes;
// 如果提供的URL将成功匹配任一个已注册的路由则返回YES。否则返回NO。
+ (BOOL)canRouteURL:(nullable NSURL *)URL;
// 如果提供的ULR将成功为当前scheme匹配任一个已注册的路由则返回YES。否则返回NO。
- (BOOL)canRouteURL:(nullable NSURL *)URL;
// 路由一个URL,为与此URL相匹配的模式调用handler blocks,直到找到了相匹配的模式,返回YES。如果没有找到匹配的路由,将调用unmatchedURLHandler(如果设置了)
+ (BOOL)routeURL:(nullable NSURL *)URL;
// 在特定scheme内路由一个URL,为与此URL相匹配的模式调用handler blocks,直到找到了相匹配的模式,返回YES。如果没有找到匹配的路由,将调用unmatchedURLHandler(如果设置了)
- (BOOL)routeURL:(nullable NSURL *)URL;
// 在任一个路由scheme内路由一个URL,调用handler blocks(模式与URL匹配),找到了则返回YES。其他参数传递给匹配的路由block
+ (BOOL)routeURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary<NSString *, id> *)parameters;
// 在任一个路由scheme内路由一个URL,调用handler blocks(模式与URL匹配),找到了则返回YES。其他参数传递给匹配的路由block
- (BOOL)routeURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary<NSString *, id> *)parameters;
// 配置详细日志记录,默认为NO
+ (void)setVerboseLoggingEnabled:(BOOL)loggingEnabled;
// 返回当前详细日志记录启用状态。默认为NO
+ (BOOL)isVerboseLoggingEnabled;
// 配置是否应将“+”替换为解析值的空格。默认为YES。
+ (void)setShouldDecodePlusSymbols:(BOOL)shouldDecode;
// 返回是否应该将"+"替换为解析值得空格。默认为YES。
+ (BOOL)shouldDecodePlusSymbols;
// 配置URL主机是否始终被视为路径组件。默认为NO。
+ (void)setAlwaysTreatsHostAsPathComponent:(BOOL)treatsHostAsPathComponent;
// 返回URL主机是否始终被视为路径组件。默认为NO。
+ (BOOL)alwaysTreatsHostAsPathComponent;
// 配置创建路由对象时使用的默认类。默认是JLRRouteDefinition类
+ (void)setDefaultRouteDefinitionClass:(Class)routeDefinitionClass;
// 返回当创建路由对象时使用的默认类。默认是JLRRouteDefinition类。
+ (Class)defaultRouteDefinitionClass;
- JLRParsingUtilities
// 根据是否解码"+"来返回value的值
+ (NSString *)variableValueFrom:(NSString *)value decodePlusSymbols:(BOOL)decodePlusSymbols;
// 根据是否解码“+”来返回查询参数
+ (NSDictionary *)queryParams:(NSDictionary *)queryParams decodePlusSymbols:(BOOL)decodePlusSymbols;
// 为路由模式展开可选路由模式
+ (NSArray <NSString *> *)expandOptionalRoutePatternsForPattern:(NSString *)routePattern;
- JLRRouteDefinition
此类表示注册路由的具体模型对象,包括URL scheme,路由模式,和优先级。这个类可以被子类化来自定义路由解析行为,通过重写-routeResponseForRequest:方法。-callHandlerBlockWithParameters也可以被重写来自定义传入handler block的参数。
// 此路由应用的URL scheme, 如果是全局的则是JLRoutesGlobalRoutesScheme
@property (nonatomic, copy, readonly) NSString *scheme;
// 路由模式
@property (nonatomic, copy, readonly) NSString *pattern;
// 此路由模式的优先级
@property (nonatomic, assign, readonly) NSUInteger priority;
// 路由模式路径组件
@property (nonatomic, copy, readonly) NSArray <NSString *> *patternPathComponents;
// 当发现匹配时调用的block
@property (nonatomic, copy, readonly) BOOL (^handlerBlock)(NSDictionary *parameters);
// 检查路由模型是否相等
- (BOOL)isEqualToRouteDefinition:(JLRRouteDefinition *)routeDefinition;
// 创建一个新的路由模型。已经创建的路由模型可以直接添加到JLRoutes实例中。
// 参数一:完整的路由模式('/foo/:bar')
// 参数二:路由优先级,默认为0
// 参数三:当发现匹配成功处理事件的回调
- (instancetype)initWithPattern:(NSString *)pattern priority:(NSUInteger)priority handlerBlock:(BOOL (^)(NSDictionary *parameters))handlerBlock
// 当为给定的scheme已经注册了路由时调用
- (void)didBecomeRegisteredForScheme:(NSString *)scheme;
// 为提供的JLRRouteRequest创建并提供一个JLRRouteResponse。此响应指定是否匹配。
// 参数: JLRRouteRequest用来创建一个响应
// 返回值:一个JLRRouteResponse实例,表示尝试将请求匹配到路由模型的结果
- (JLRRouteResponse *)routeResponseForRequest:(JLRRouteRequest *)request;
// 使用给定的参数调用handlerBlock。可以被子类重写。
// 参数:传递给handlerBlock的参数
// 返回值:调用handlerBlock的返回值(如果被视为已经处理了则返回YES,否则返回NO)
- (BOOL)callHandlerBlockWithParameters:(NSDictionary *)parameters;
// 创建并返回完整的匹配参数集合,作为有效匹配的一部分传递。子类可以重写此方法来修改匹配参数,或者直接调用它来生成期望值。
// 参数request: 被路由的请求
// 参数routeVariables: 解析的路由变量(又名 '/route/:param'路由被'/foo/bar'路由会创建['param' : 'bar'])
// 返回值:完整的匹配参数集合,左右有效匹配的一部分传递。
- (NSDictionary *)matchParametersForRequest:(JLRRouteRequest *)request routeVariables:(NSDictionary <NSString *, NSString *> *)routeVariables;
// 为给定的请求创建并返回一个默认的基本匹配参数。不包含任何解析字段
// 参数request: 被路由的请求
// 返回值:给定请求的默认匹配参数。只包含JLRoutePatternKey,JLRouteURLKey和JLRouteSchemeKey的键/值对
- (NSDictionary *)defaultMatchParametersForRequest:(JLRRouteRequest *)request;
// 为给定的请求解析并返回路由变量
// 参数request: 从中解析变量值的请求
// 返回值:如果是匹配的则返回解析的路由变量,如果没有匹配则返回nil
- (nullable NSDictionary <NSString *, NSString *> *)routeVariablesForRequest:(JLRRouteRequest *)request;
// 将value解析为变量名称,包括如果需要删除任何额外的字符。
// 参数value: 应该被解析为变量名称的原始字符串值
// 返回值: 变量名称,用作分析路由变量中键/值对的键
- (NSString *)routeVariableNameForValue:(NSString *)value;
// 将value解析为变量值,包括如果需要删除任何额外的字符
// 参数value: 应该被解析为变量值得原始字符串值
// 返回值:变量值,用作分析路由变量中键/值对的值
- (NSString *)routeVariableValueForValue:(NSString *)value;
- JLRRouteHandler
是创建handler blocks的帮助类,用于传递给addRoute:调用。这对于希望单独的类或对象成为深层链接路由的处理程序的情况特别有用。比如可能是一个你想要实例化和呈现的视图控制器类来响应深层链接路由。
// 创建并返回一个在weak属性目标对象上调用handleRouteWithParameters:方法的block。从此方法返回的block应该作为addRoute:的handler block。
// 参数weakTarget: 应该处理匹配路由的目标对象
// 返回值 提供的weakTarget的新处理块
// 讨论:目标对象的拥有者没有变化,只有一个弱指针在block中被捕获。如果该对象被重新分配,则handler将不再被调用(但路由将保持注册,除非明确移除)
+ (BOOL (^__nonnull)(NSDictionary<NSString *, id> *parameters))handlerBlockForWeakTarget:(__weak id <JLRRouteHandlerTarget>)weakTarget;
// 创建并返回一个创建新实例的targetClass(此targetClass必须遵守JLRRouteHandlerTarget协议)的block,然后在它上调用handleRouteWithParameters:方法。然后创建的对象作为参数传递给完成的block块。从此方法返回的block应该作为addRoute:的handler block。
// 参数targetClass: 要处理路由请求的目标类。必须遵守JLRRouteHandlerTarget协议。
// 参数completionHandler: 创建了一个新targetClass实例后调用的完成块。
// 返回值 用于创建targetClass实例的新的handler block。
// 讨论:JLRoutes不会保留或拥有此创建的对象。预计通过完成的回调传递的创建对象将被调用应用程序使用并拥有。
+ (BOOL (^__nonnull)(NSDictionary<NSString *, id> *parameters))handlerBlockForTargetClass:(Class)targetClass completion:(BOOL (^)(id <JLRRouteHandlerTarget> createdObject))completionHandler;
- JLRRouteHandlerTarget
遵守此协议的类才可以被用来作为路由处理目标
// 从JLRoutes路由里通过传递匹配的路由参数初始化一个遵守此协议的类的实例。
// 参数:初始化对象时传递的匹配参数。这些从JLRoutes handler block里传递。
// 返回值:遵守此协议的类的实例
- (instancetype)initWithRouteParameters:(NSDictionary <NSString *, id> *)parameters;
// 为成功的路由匹配调用
// 参数:传递给handler block的匹配参数
// 返回值: 如果路由已经处理则返回YES,如果应该尝试匹配不同的路由则返回NO
- (BOOL)handleRouteWithParameters:(NSDictionary<NSString *, id> *)parameters;
- JLRRouteRequest
表示用来路由一个URL的请求模型。它被分解为路径组件和查询参数,然后由JLRRouteDefinition使用它们来尝试匹配
// 被路由的URL
@property (nonatomic, copy, readonly) NSURL *URL;
// URL的路径组件
@property (nonatomic, strong, readonly) NSArray *pathComponents;
// URL的查询参数
@property (nonatomic, strong, readonly) NSDictionary *queryParams;
// 路由请求选项,通常由框架全局选项配置
@property (nonatomic, assign, readonly) JLRRouteRequestOptions options;
// 作为匹配参数字典的一部分传递的其他参数
@property (nonatomic, copy, nullable, readonly) NSDictionary *additionalParameters;
// 创建一个新路由请求
// 参数URL: 用来路由的URL
// 参数options: 指定解析行为的选项位掩码。
// 参数additionalParameters: 包含在针对此请求的任何匹配字典中的其他参数
// 返回值:一个新的路由请求实例
- (instancetype)initWithURL:(NSURL *)URL options:(JLRRouteRequestOptions)options additionalParameters:(nullable NSDictionary *)additionalParameters
- JLRRouteResponse
尝试路由JLRRouteRequest的响应
// 指示响应是否匹配
@property (nonatomic, assign, readonly, getter=isMatch) BOOL match;
// 匹配参数(一个无效的响应为nil)
@property (nonatomic, copy, readonly, nullable) NSDictionary *parameters;
// 检查路由响应是否相等
- (BOOL)isEqualToRouteResponse:(JLRRouteResponse *)response;
// 创建一个无效的匹配响应(不匹配)
+ (instancetype)invalidMatchResponse;
// 使用给定的参数创建一个有效的匹配响应
+ (instancetype)validMatchResponseWithParameters:(NSDictionary *)parameters;
方法调用流程图
主要内部实现
// 根据scheme返回路由实例(JLRoutes实例)
+ (instancetype)routesForScheme:(NSString *)scheme
{
// 声明JLRoutes实例对象routesController
JLRoutes *routesController = nil;
// 创建可变全局字典JLRGlobal_routeControllersMap,只创建一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
JLRGlobal_routeControllersMap = [[NSMutableDictionary alloc] init];
});
// 如果字典中没有key为schme对应的Value, 就创建一个,否则直接获取
if (!JLRGlobal_routeControllersMap[scheme]) {
routesController = [[self alloc] init]; // 初始化routesController
routesController.scheme = scheme;// 将scheme设置为routesController的Scheme
JLRGlobal_routeControllersMap[scheme] = routesController; // JLRGlobal_routeControllersMap字典中添加key为scheme和value为routesController的键值对
}
// 从JLRGlobal_routeControllersMap中根据key为scheme获取对应的routesController并返回
routesController = JLRGlobal_routeControllersMap[scheme];
return routesController;
}
// 参数一: 路由模式
// 参数二:优先级
// 参数三: 处理回调
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary<NSString *, id> *parameters))handlerBlock
{
// 为routePattern路由模式展开可选路由模式
NSArray <NSString *> *optionalRoutePatterns = [JLRParsingUtilities expandOptionalRoutePatternsForPattern:routePattern];
// 根据传入参数创建JLRRouteDefinition路由模型对象
JLRRouteDefinition *route = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:routePattern priority:priority handlerBlock:handlerBlock];
// 如果optionalRoutePatterns大于0, 即有可选路由模式
if (optionalRoutePatterns.count > 0) {
// there are optional params, parse and add them
for (NSString *pattern in optionalRoutePatterns) {
// 遍历路由模式,根据路由模式和其他参数创建路由模型对象
JLRRouteDefinition *optionalRoute = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:pattern priority:priority handlerBlock:handlerBlock];
// 添加到mutableRoutes数组中视为注册
[self _registerRoute:optionalRoute];
// 打印日志
[self _verboseLog:@"Automatically created optional route: %@", optionalRoute];
}
return;
}
// 如果optionalRoutePatterns为空数组,则将传递进来的路由模式生成的路由模型添加到mutableRoutes数组
[self _registerRoute:route];
}
// 私有方法, 注册路由
// 参数: 路由模型对象
// 将routeDefinition路由模型对象按优先级从高到低添加到mutableRoutes数组中,并将self的scheme赋值给该路由模型的scheme
- (void)_registerRoute:(JLRRouteDefinition *)route
{
// 如果路由模型对象(路由模式)的优先级为0 或者 mutableRoutes数组为0
if (route.priority == 0 || self.mutableRoutes.count == 0) {
//mutableRoutes数组添加路由模型对象
[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]) { // 此处用copy拷贝一份数组为不可变,如果不copy,在下方给self.mutableRoutes插入模型时会造成崩溃,注意:遍历数组时不能修改数组。
// 如果传递近来的路由模型优先级高于mutableRoutes数组中某个已经存在的路由模型优先级,则插入到数组中
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
//addedRoute为NO,则说明传递进来的模型优先级低于数组中每一个模型优先级,就添加到数组的最后一个
if (!addedRoute) {
[self.mutableRoutes addObject:route];
}
}
// 通过上面插入排序的方法,保证mutableRoutes数组中的JLRRouteDefinition对象是按照优先级的从高到底排列的
// 将JLRoutes的scheme赋值给传递进来的路由模型对象的scheme
[route didBecomeRegisteredForScheme:self.scheme];
}
// 私有方法 匹配路由,是否已路由
// 参数一:路由URL
// 参数二:parameters
// 参数三: 执行block
- (BOOL)_routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters executeRouteBlock:(BOOL)executeRouteBlock
{
if (!URL) { // 如果URL为空
return NO;
}
[self _verboseLog:@"Trying to route URL %@", URL];
BOOL didRoute = NO; // 标记是否已经路由
JLRRouteRequestOptions options = [self _routeRequestOptions]; // 获取路由请求选项
// 创建路由请求
JLRRouteRequest *request = [[JLRRouteRequest alloc] initWithURL:URL options:options additionalParameters:parameters];
for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
// check each route for a matching response
//根据request查找对应匹配的response, 是否匹配成功
JLRRouteResponse *response = [route routeResponseForRequest:request];
if (!response.isMatch) { // 如果匹配不成功,下一个
continue;
}
// 匹配成功
[self _verboseLog:@"Successfully matched %@", route];
if (!executeRouteBlock) {
// if we shouldn't execute but it was a match, we're done now
// 没有执行block立即返回
return YES;
}
[self _verboseLog:@"Match parameters are %@", response.parameters];
// Call the handler block
// 使用响应的参数调用handlerBlock
didRoute = [route callHandlerBlockWithParameters:response.parameters];
if (didRoute) { // 如果已路由,中断循环
// if it was routed successfully, we're done - otherwise, continue trying to route
break;
}
}
if (!didRoute) {
[self _verboseLog:@"Could not find a matching route"];
}
// if we couldn't find a match and this routes controller specifies to fallback and its also not the global routes controller, then...
// 1. 如果没有路由
// 2. 尝试将URL与全局路由匹配
// 3. 当前self不是全局路由
if (!didRoute && self.shouldFallbackToGlobalRoutes && ![self _isGlobalRoutesController]) {
[self _verboseLog:@"Falling back to global routes..."];
// 尝试用全局路由来匹配
didRoute = [[JLRoutes globalRoutes] _routeURL:URL withParameters:parameters executeRouteBlock:executeRouteBlock];
}
// if, after everything, we did not route anything and we have an unmatched URL handler, then call it
// 1. 没有路由
// 2. 执行路由block
// 3. 有不匹配路由的回调
if (!didRoute && executeRouteBlock && self.unmatchedURLHandler) {
[self _verboseLog:@"Falling back to the unmatched URL handler"];
// 回调不匹配路由
self.unmatchedURLHandler(self, URL, parameters);
}
// 返回是否已路由
return didRoute;
}
总结:
JLRouts
中有一个全局可变字典JLRGlobal_routeControllersMap
, 该字典是以scheme
为key
,以JLRoutes
对象为value
;JLRoutes
对象有一个可变数组mutableRoutes
,里面存放着JLRRouteDefinition
对象,该对象的scheme
是JLRoutes
的scheme
,并且有一个优先级属性priority
;mutableRoutes
每次添加JLRRouteDefinition
对象时都会进行插入排序,优先级从高到底。- 调用
routesForScheme:
方法时,会将scheme
添加到JLRGlobal_routeControllersMap
字典中。- 调用
addRoute:
方法时,会创建JLRRouteDefinition
路由模型对象添加到mutableRoutes
数组中。JLRRouteDefinition
路由模型对象会保存addRoute:
传递进来的handlerBlock
。- 调用
routeURL:
方法时,会根据传递进来的参数创建JLRRouteRequest
对象,并遍历mutableRoutes
数组,找到与之匹配的JLRRouteResponse
对象,处理handlerBlock
。
有写得不好的地方还希望多多指点哈~