介紹
JLRoutes是一個URL解析庫,可以很方便的處理不同URL schemes以及解析它們的參數(shù),并通過回調(diào)block來處理URL對應(yīng)的操作。
使用場景
對一個App中單獨的模塊,可以使用openURL的方式進行頁面跳轉(zhuǎn),很好地解耦不同的模塊铃绒,蘑菇街的組件化之路就是基于URL跳轉(zhuǎn)的方式,當然casa也提出了Target—Action模式下配合category實現(xiàn)的組件化架構(gòu)螺捐,時隔幾個月又重寫看了兩位大神的文章匿垄,感覺腦細胞真的不夠用啊。
我沒有組件化的經(jīng)驗归粉,所以使用JSRoutes僅限于遠程調(diào)用(服務(wù)端下發(fā)椿疗、Push跳轉(zhuǎn)等),本地調(diào)用還是不太敢用這種URL的統(tǒng)一跳轉(zhuǎn)糠悼。
使用實例
先來個簡單的使用demo
在didFinishLaunchingWithOptions中注冊所有的URL
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[JLRoutes addRoute:@"/:controller" handler:^BOOL(NSDictionary *parameters) {
NSString *controller = parameters[@"controller"];
[self.window.rootViewController presentViewController:[[NSClassFromString(controller) alloc] init] animated:YES completion:^{
}];
return YES;
}];
return YES;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [JLRoutes routeURL:url];
}
打開指定URL資源
NSURL *viewUserURL = [NSURL URLWithString:@"myapp://user/view/joeldev"];
[[UIApplication sharedApplication] openURL:viewUserURL];
原理
JLRoutes本質(zhì)可以理解為:保存一個全局的Map届榄,key是url,value是對應(yīng)的block倔喂,url和block都會常駐在內(nèi)存中铝条,這也是為什么casa反對使用URL跳轉(zhuǎn)來實現(xiàn)組件化的原因,當注冊的url很多了席噩,對內(nèi)存的消耗也是很大的班缰。當打開一個URL時,JLRoutes就可以遍歷這個全局的map悼枢,通過url來執(zhí)行對應(yīng)的block埠忘。
內(nèi)部實現(xiàn)
namespace
routeControllersMap是一個NSDictionary類型的單例,key是namespace,value是一個array莹妒,里面包含當前namespace下所有的routes名船。
命名空間也對應(yīng)我們的URL scheme。
globalRoutes返回全局命名空間
+ (instancetype)globalRoutes {
return [self routesForScheme:kJLRoutesGlobalNamespaceKey];
}
routesForScheme返回指定scheme對應(yīng)的命名空間旨怠,如果不存在就創(chuàng)建一個
+ (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.namespaceKey = scheme;
routeControllersMap[scheme] = routesController;
}
routesController = routeControllersMap[scheme];
return routesController;
}
添加Route
注冊一個Route到global scheme namespace渠驼,并設(shè)置其優(yōu)先級(默認優(yōu)先級是0),block返回一個bool鉴腻,如果返回YES表示當前匹配成功迷扇,如果返回NO表示繼續(xù)匹配其他Route(_JLRoute對象)
一個內(nèi)部類,用下劃線開頭命名_JLRoute,其結(jié)構(gòu)如下:
@interface _JLRoute : NSObject
@property (nonatomic, weak) JLRoutes *parentRoutesController;
@property (nonatomic, strong) NSString *pattern;
@property (nonatomic, strong) BOOL (^block)(NSDictionary *parameters);
@property (nonatomic, assign) NSUInteger priority;
@property (nonatomic, strong) NSArray *patternPathComponents;
- (NSDictionary *)parametersForURL:(NSURL *)URL components:(NSArray *)URLComponents;
@end
addRoute這個方法對原始routePattern字符串做一個加工和過濾的操作爽哎,如去掉圓括號
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary *parameters))handlerBlock {
// if there's a pair of parenthesis, process optionals, trim the parenthesis, put it on trimmedRoute
NSString *trimmedRoute = routePattern;
// repeat until no parenthesis pair is found
while ([trimmedRoute rangeOfString:@")" options:NSBackwardsSearch].location > [trimmedRoute rangeOfString:@"(" options:NSBackwardsSearch].location) {
//Build route with the optionals
NSString *patternWithOptionals = [trimmedRoute stringByReplacingOccurrencesOfString:@"(" withString:@""];
patternWithOptionals = [patternWithOptionals stringByReplacingOccurrencesOfString:@")" withString:@""];
[self registerRoute:patternWithOptionals priority:priority handler:handlerBlock];
//Build route without optionals
NSRange rangeOfLastParentheses = [trimmedRoute rangeOfString:@"(" options:NSBackwardsSearch];
NSRange rangeToRemove = NSMakeRange(rangeOfLastParentheses.location, trimmedRoute.length - rangeOfLastParentheses.location);
NSString *patternWithLastOptionalRemoved = [trimmedRoute stringByReplacingCharactersInRange:rangeToRemove withString:@""];
//Remove any parenthesis for other optionals that might still be in the route
NSString *patternWithoutOptionals = [patternWithLastOptionalRemoved stringByReplacingOccurrencesOfString:@"(" withString:@""];
patternWithoutOptionals = [patternWithoutOptionals stringByReplacingOccurrencesOfString:@")" withString:@""];
[self registerRoute:patternWithoutOptionals priority:priority handler:handlerBlock];
trimmedRoute = patternWithLastOptionalRemoved;
}
//Only register original route if trimmedRoute haven't been modified.
if (trimmedRoute == routePattern) {
[self registerRoute:routePattern priority:priority handler:handlerBlock];
}
}
registerRoute這個方法就是把_JLRoute插入到routeControllersMap[kJLRoutesGlobalNamespaceKey]這個list中的時候蜓席,用到了插入排序的思想,priority高的在前面倦青。這樣enum這個_JLRoute List的時候,如果match pattern盹舞,就return产镐,自然就解決了「路徑匹配優(yōu)先級」的問題。
- (void)registerRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(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 = [^BOOL (NSDictionary *params) {
return 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
// 找到一個優(yōu)先級低的踢步,采用插入排序
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];
}
}