最近在给项目引入路由功能。
路由是指根据url分配到对应的处理程序,在移动端就可以理解为,根据给定的url跳转到指定页面的功能模块。
iOS:
TestViewController *vc = [[TestViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
Android:
Intent intent = **new** Intent();
intent.setClass(Intent1Activity.**this**,
Intent2Activity.**class**);
intent.startActivity(intent);
iOS:
[Router openURL:@"xxx://xxx.com/live/player?id=200"];
Android(直接扣的ARouter的代码):
// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
NSURLComponent
类中都有对应的字段。所以URL可以通过NSURLComponent
来进行解析。
对应到移动端的路由可能是这样的:
商品详情:xxx://xxx.com/mall/goods/detail?id=2003
URL的定义应该比较直观,有一定的意义,让人一看就大概能知道这个URL的作用。
读了一些github上的开源路由库的代码,实现的原理都是一样的,用一个集合类缓存路由跟处理闭包的对应关系,在app启动的时候,注册好支持的URL,当调用openURL的时候,根据给定的URL查找到对应的闭包,执行闭包代码,实现跳转。找不到对应的闭包的时候,可以定义一个公共的错误页面。
还有一种是保存url跟类名的对应关系,在openURL的时候,找到对应的类名,通过runtime生成实例,实现统一跳转。
在注册url的时候,自己处理openUrl的block,中间件会提供一个当前的viewcontroller给组件做跳转用。
[MGJRouter registerURLPattern:@"test://test/test" toHandler:^(NSDictionary *routerParameters) {
// 注册的时候自己处理 push present
// 注册的地方在自己的模块里面,所以,自己可以拿到viewController的实例
}];
[MGJRouter registerURLPattern:@"test://push/:controller" toHandler:^(NSDictionary *routerParameters) {
UIViewController *currentVc = [self currentViewController];
UIViewController *v = [[NSClassFromString(parameters[@"controller"]) alloc] init];
// 通过runtime设置参数
[self paramToVc:v param:parameters];
[currentVc.navigationController pushViewController:v animated:YES];
return YES;
}];
注册的时候保存好路由跟类名字符串的一一对应关系,openURL的时候通过url找到对应的类名,通过runtime生成实例,进行跳转。
保存一个如下这样的routes关系:
@{
@"http" : @"YTWebViewController",
@"https" : @"YTWebViewController",
JXSG_SCHEME: @{
PATH_SEARCH_RESULT : @"SearchResultViewController",
PATH_CATEGORY : @"CategoryViewController",
},
}
里面的viewController的类名对应的键是路由的path。如下:
myscheme://baidu.com/mall/category
这个路由对应的routes关系是 : @"/mall/category" : @"CategoryViewController" 调用的时候像这样:
[YTRouter pushURL:@"myscheme://xxx.com/xxx/xxx?p1=v1&p2=v2];
这里p1,p2的参数名称要跟view controller里面的参数名称一致,这样才能用runtime把参数设置到view controller里面去。
我们最后选择了第三种方案,实际集成的时候还是有好多其它的问题。这里列举一些:
runtime给view controller设置参数
-(void)setViewController:(UIViewController *) v parameters:(NSDictionary<NSString *,NSString *> *)parameters {
unsigned int propertiesCount = 0;
objc_property_t * properties = class_copyPropertyList(v.class , & propertiesCount);
for (int i = 0; i < propertiesCount; i++) {
objc_property_t property = properties[i];
NSString *key = [NSString stringWithUTF8String:property_getName(property)];
NSString *param = parameters[key];
if (param != nil) {
[v setValue:param forKey:key];
}
}
}
纯粹的使用URL来做跳转,意义并不大。免去了查找view controller的过程,却引入了查找URL的过程,URL不被支持后还得清理。而且调用的参数也没有直接调用来得直观、明确。参数也不好以对象的形式传递。
路由的主要作用应该是解耦,但是如果不组件化,还是会存在各种各样的耦合。耦合太多的话,会增加测试的难度。组件间通讯解耦就不局限于路由了,可以面向接口编程,通过接口来对外暴露模块支持的功能。