JLRoutes源码分析
本文为系列文章读源代码学习iOS开发中的一篇,相关文章:
JLRoutes是一个非常简单(还是只有两个文件)好用的URL map库。对于功能区分比较明显的app,比如:HubSpot
点击左侧菜单的每个链接都可以到一个单独的功能里面,非常适合拆分,然后用JLRoutes组合。
JLRoutes本质可以理解为:保存一个全局的Map,key是url,value是对应的block。这样在下面的代码中:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [JLRoutes routeURL:url];
}
如果自己被打开:
NSURL *viewUserURL = [NSURL URLWithString:@"myapp://user/view/joeldev"];
[[UIApplication sharedApplication] openURL:viewUserURL];
JLRoutes就可以遍历这个全局的map,通过url来执行对应的block。
像这种全局的变量,首先想到肯定要用Singleton模式吧。不过和普通的单利稍有区别,这个property不是self,而是一个成员变量。注意dispatch_once
的使用。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
routeControllersMap = [[NSMutableDictionary alloc] init];
});
但是注意,JLRoutes中的routeControllersMap可不是刚才提到的全局map,而是namespace。这样,JLRoutes找对应url的block的时候,首先要找namespace,默认的全局namespace是:
static NSString *const kJLRoutesGlobalNamespaceKey = @"JLRoutesGlobalNamespace"
而且每个namespace下存放的不是map而是list。这个list里面存放的是:
@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
_JLRoute
插入到routeControllersMap[kJLRoutesGlobalNamespaceKey]这个list中的时候,用到了插入排序
的思想,priority高的在前面。这样enum这个_JLRoute List的时候,如果match pattern,就return,自然就解决了「路径匹配优先级」的问题。
##block的copy
在插入新的规则的时候:
- (void)addRoute:(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];//注意这里的copy
route.parentRoutesController = self;
if (!route.block) {
route.block = [^BOOL (NSDictionary *params) {
return YES;
} copy];//注意这里的copy
}
//此处代码省略
}
为什么要对block进行copy呢?具体原因可以参考正确使用Block避免Cycle Retain和Crash。简单的说一下,由于block可能引入block外部的变量(比如int a吧),但是a是有作用域的,但是传递block的时候,可能在block引用的地方,超过了a的作用域。所以程序虽然能通过编译,但是运行的时候会提示
Segmentation fault: 11
举个例子:
# import <Foundation/Foundation.h>
typedef long (^BlkSum)(int, int);
NSArray *test;
void setBlock(){
int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // blk2 = <__NSStackBlock__: 0xbfffddf8>
// test=@[[blk2 copy]];
test=@[blk2 ];
}
int main(int argc,const char *argv[]){
setBlock();
NSLog(@"%ld",((BlkSum)(test[0]))(1,2));
}
//运行:gcc -ObjC -framework Foundation test.m && ./a.out
相反,如果用copy的话,就没问题,因为copy会把block引用的外部变量一块copy出去。科学一点的解释是,block有三种位置:
- NSGlobalBlock:类似函数,位于text段;
- NSStackBlock:位于栈内存,函数返回后Block将无效;
- NSMallocBlock:位于堆内存。
其中NSGlobalBlock
类型的block和普通函数一样,不引用外部变量,不需要copy。第二种NSStackBlock
引用外部变量,会收到外部变量的作用域限制,所以需要copy成NSMallocBlock
。
##使用JLRoutes
在我们的项目中,即使不用JLRoutes来组合sub app,也建议引入,因为:
- 本身很小,可以直接拖进来,不用cocoapods。
- 很方便扩展自己的app,比如从触屏版的产品页面打开,或者扫描二维码进来,这样都是一个url,可以很方便的处理这些请求。
JLRoutes不适合的场景:(正在发邮件问limboy)
比如我现在是在产品单页上,点击进入店铺跳转到店铺。这时候很显然是
[self.navController pushViewController:<#(UIViewController *)#> animated:YES];
因为可以很方便的返回。如果这时候用了:
[[UIApplication sharedApplication] openURL:@"myapp://shop/123"];
跳转到对应的shop问题不大,这时候返回就不好处理了。如果当前页面保留了复杂的用户用户输入、改变,在返回问题更大。
参考文档: