iOS路由介绍
iOS路由目前业内流行的有两大分类:1、基于URL或protocol的注册调度型路由 2、runtime调度型路由
其中protocol类型的路由查的资料中目前只找到了一个MGJRouter,就是蘑菇街路由,但是这个主库已经被删了,所以这个类型的就不再讨论了。
下面为大家整理了一下这两种类型路由各自的原理、优缺点、常见的第三方库等,参见下表:
路由类型 | 原理 | 优点 | 缺点 | 常见第三方库 |
---|---|---|---|---|
基于URL类型的路由 | 借鉴前端Router和系统App内跳转的方式,通过URL来完成路由请求。 较为详细的描述,就是像前端页面那样,一个URL对应一个页面,将这些对应的关系保存到一个注册表,而路由的过程就是拿着目标页面的URL,通过保存URL和页面对应关系的注册表,去查找这个URL对应的页面。 |
1. 服务器可以动态的控制页面跳转,可以统一处理页面出问题之后的错误处理。 2. 可以统一三端,iOS,Android,H5 / RN / Weex 的请求方式。 |
1.需要在load方法中注册,影响app启动速度 URL类型路由中页面和URL的对应规则是需要注册的,并且现行的第三方库关于这类操作都是在load方法里面写,而写在load方法里面是会影响App启动速度的; 2. URL涉及硬编码且其中的参数不易维护 URL链接里面关于组件和页面的名字都是硬编码,参数也都是硬编码。而且每个URL参数字段都必须要一个文档进行维护,这个对于业务开发人员也是一个负担。而且URL短连接散落在整个App四处,维护起来比较麻烦。 3. 对象类型参数需要使用字典实现 对于传递NSObject的参数,URL是不够友好的,它最多是传递一个字典。 |
JLRoutes Routable-ios HHRouter ALRouter |
基于runtime类型的路由 | 主要是基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用。具体描述就是:请求路由时,根据路由信息中的target和action,利用objective-C的runtime特性转化生成target实例,再利用这个target实例,调用对应的action操作,完成路由。 看完是不是不太好理解,简单打比方就是把路由目标比成月饼,action就是扣月饼的模子,target就是装模子的盒子。具体意义可以参看下面的例子。 |
1. 无需注册 充分的利用Runtime的特性,无需注册这一步。Target-Action方案只有存在组件依赖Mediator这一层依赖关系。在Mediator中维护针对Mediator的Category,每个category对应一个Target,Categroy中的方法对应Action场景。 2. 统一组件调用入口 Target-Action方案也统一了所有组件间调用入口。 3. 有校验保证安全 Target-Action方案也能有一定的安全保证,它对url中进行Native前缀进行验证。 |
1. 涉及硬编码 Target_Action在Category中将常规参数打包成字典,在Target处再把字典拆包成常规参数,这就造成了一部分的硬编码。 |
CTMediator |
下面我再针对表中常见的第三方库的详细使用方法进行介绍:
1. 基于URL类型的路由
1.1 JLRoutes
5.5k Star
1.1.1 路由准备
1.1.1.1:
在BaseViewController和BaseTabBarController中添加设置参数方法,供子方法继承(统一处理)
- (void)setParameter(NSString*)parameter;
1.1.1.2:添加接收到路由后的处理操作(统一处理)
一般放在AppDelegate中的didFinishLaunchingWithOptions方法中进行配置;需要集中管理的话得自己去写工具类统一调度
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
JLRoutes *routes = [JLRoutes globalRoutes];
[routes addRoute:@"/open/:targetVc/:parameter/:openType" handler:^BOOL(NSDictionary *parameters) {
// 获取路由中配置的要跳转的页面、参数及跳转方式
NSString *targetVcString = parameters[@"targetVc"];
NSString *parameter = parameters[@"parameter"];
NSString *openType = parameters[@"openType"];
// 根据页面字符串获取页面对象
Class targetClass = NSClassFromString(targetVcString);
id targetVc = [[targetClass alloc] init];
// 传入参数
if ([targetVc respondsToSelector:@selector(setParameter:)]) {
[targetVc performSelector:@selector(setParameter:) withObject:parameters[@"parameter"]];
}
// 展示VC 默认push方法
if ([openType isEqualToString:@"present"]) {
[[UIUtil getCurrentVC] presentViewController:targetVc animated:YES completion:nil];
} else {
if (viewController.navigationController!=nil) {
[viewController.navigationController pushViewController:targetVc animated:YES];
} else if ([UIUtil getCurrentVC].navigationController !=nil) {
[[UIUtil getCurrentVC].navigationController pushViewController:targetVc animated:YES];
} else {
[[UIUtil getCurrentVC] presentViewController:targetVc animated:YES completion:nil];
}
}
return YES;
}];
...
}
1.1.1.3:页面处理传参
HnxxtNewsParentViewController里继承方法处理传参(页面各自处理)
- (void)setParameter(NSString*)parameter
{
NSData *jsonData = [parameter dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
self.param1 = [dict valueForKey:xxx];
}
1.1.2 路由使用
1.1.2.1:跳转到页面,无参数(页面各自处理)
NSURL *targetURL = [NSURL URLWithString:@"xxt://open/HnxxtNewsParentViewController"];
[JLRoutes routeURL:url];
1.1.2.2:跳转到页面,有参数(页面各自处理)
NSURL *targetURL = [NSURL URLWithString:[NSString stringWithFormat:@"xxt://open/HnxxtNewsParentViewController/%@",[dict JSONString]];
[JLRoutes routeURL:url];
1.1.2.2:跳转到页面,有参数,指定present方式(页面各自处理)
NSURL *targetURL = [NSURL URLWithString:[NSString stringWithFormat:@"xxt://open/HnxxtNewsParentViewController/%@/present",[dict JSONString]];
[JLRoutes routeURL:url];
1.1.3 兼容外部调用打开APP并跳转到页面(统一处理)
在AppDelegate中的 openURL 方法统一处理
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
//url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
return [JLRoutes routeURL:url];
}
1.2 JSDVCRouter
JLRoutes的封装
参考: iOS 组件化-路由解耦思想 JLRoutes 实战篇(一)App内控制器跳转
GitHub工程
1.2.1 路由准备
1.添加VC配置到路由表
#import "JSDVCRouterConfig.h"
//跳转方式、动画等常量
NSString* const kJSDVCRouteSegue = @"JSDVCRouteSegue";//跳转方式
NSString* const kJSDVCRouteAnimated = @"JSDVCRouteAnimated";//是否需要动画
NSString* const kJSDVCRouteFromOutside = @"JSDVCRouteFromOutside";//是否来自外部跳转
//跳转方式区分:push,present
NSString* const kJSDVCRouteSeguePush = @"push";
NSString* const kJSDVCRouteSegueModal = @"present";
//类名,title,特殊标志位,是否登录标志位等,只有类名是必须的,其他都可以不配置
NSString* const kJSDVCRouteClassName = @"class";
NSString* const kJSDVCRouteClassTitle = @"title";
NSString* const kJSDVCRouteClassFlags = @"flags";
NSString* const kJSDVCRouteClassNeedLogin = @"needLogin";
// 定义vc 路由的路径
NSString* const HnxxtNewsParentViewController = @"/hnxxt/HnxxtNewsParentViewController";
@implementation JSDVCRouterConfig
//将vc路径 和vc类名等信息进行配置
+ (NSDictionary *)configMapInfo {
return @{
HnxxtNewsParentViewController: @{kJSDVCRouteClassName: @"HnxxtNewsParentViewController",
kJSDVCRouteClassTitle: @"消息父页面",
kJSDVCRouteClassFlags: @"",
kJSDVCRouteClassNeedLogin: @"",
}
};
}
@end
1.2.2 路由使用
//在vc中的跳转调用:可以在parameters中配置跳转方式,跳转动画等属性,及页面参数等
//1、跳转到页面,无参数,默认push跳转
[JSDVCRouter openURL: HnxxtNewsParentViewController];
//2、跳转到页面,带参数,默认push跳转
[JSDVCRouter openURL: HnxxtNewsParentViewController parameters:@{@"param1": @"value1"}];
//3、跳转到页面,带参数,配置跳转方式为push,展示动画等
[JSDVCRouter openURL: HnxxtNewsParentViewController parameters:@{@"param1": @"value1", kJSDVCRouteSegue: kJSDVCRouteSeguePush, kJSDVCRouteAnimated:@(YES)}];
1.2.3 兼容外部调用打开APP并跳转到页面(统一处理)
在AppDelegate中的 openURL 方法统一处理
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
//按照约定的url解析后,组合参数、跳转动画等信息,再调用内部路由方法:
return [JSDVCRouter openURL: HnxxtNewsParentViewController parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"param1": @"value1"}];
}
1.3 Routable-ios
1.8k Star
1.3.1 路由准备
1.3.1.1 在AppDelegate中的didFinishLaunchingWithOptions方法中进行路由注册及导航栏设置;需要集中管理的话得自己去写工具类统一调度
@implementation UPAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UINavigationController *nav = [[UINavigationController alloc] initWithNibName:nil bundle:nil];
//注册:默认注册 参数可以在url里边设置,也可以用另一个注册方法的API设置参数
[[Routable sharedRouter] map:@"/hnxxt/HnxxtNewsParentViewController/:key1" toController:[HnxxtNewsParentViewController class]];
//另外 注册时可以通过设置UPRouterOptions来配置目标vc需要的参数,跳转方式,跳转动画等
// 详情参看下面UPRouterOptions的初始化方法介绍
[[Routable sharedRouter] map:@"/hnxxt/HnxxtNewsParentViewController" toController:[HnxxtNewsParentViewController class] withOptions:[UPRouterOptions routerOptions]];
//可以在注册的时候设置导航栏;或者在跳转的地方,跳转前设置导航栏也行
[[Routable sharedRouter] setNavigationController:nav];
[self.window setRootViewController:nav];
[self.window makeKeyAndVisible];
return YES;
}
@end
//UPRouterOptions 的标准初始化方法
/**
@param presentationStyle 目标UIViewController弹出时的风格`UIModalPresentationStyle`
@param transitionStyle 目标UIViewController动画风格 `UIModalTransitionStyle`
@param defaultParams 目标UIViewController的参数,也即可以通过url配置的参数
@param isRoot 是否设置目标vc为当前vc栈的根vc
@param isModal 是否模态弹出
*/
+ (instancetype)routerOptionsWithPresentationStyle: (UIModalPresentationStyle)presentationStyle
transitionStyle: (UIModalTransitionStyle)transitionStyle
defaultParams: (NSDictionary *)defaultParams
isRoot: (BOOL)isRoot
isModal: (BOOL)isModal;
1.3.1.2 在目标UIViewController中实现方法:initWithRouterParams: ,也就是如果用这个库,工程里的vc都得实现这个方法
@implementation HnxxtNewsParentViewController
// params will be non-nil
- (id)initWithRouterParams:(NSDictionary *)params {
if ((self = [self initWithNibName:nil bundle:nil])) {
self.param1 = [params objectForKey:@"key1"];
}
return self;
}
@end
1.3.2 路由使用
在需要跳转的地方调用:
NSString *url = @"/hnxxt/HnxxtNewsParentViewController/param1";
//直接跳转
[[Routable sharedRouter] open:url];
//通过api传入额外的参数,看源码这个extraParams的参数也会被在目标vc的initWithRouterParams方法中解析,并且它的
//优先级比url中参数优先级高,同名会覆盖url中设置的参数
[[Routable sharedRouter]open:url animated:animated extraParams:extraParams]];
1.3.3 兼容外部调用打开APP并跳转到页面(统一处理)
查看源码是没有提供处理外部应用打开当前app的相关路由方法,只提供了一个:openExternal:(NSString *)url 方法,该方法只是用来打开app外部应用的方法。所以对于外部打开app处理还是同上述一致:
在AppDelegate中的 openURL 方法统一处理
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
//url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
//将url信息处理成 runtable-iOS 库合适的跳转信息进行跳转处理
return [[Routable sharedRouter]open:url animated:animated extraParams:extraParams]];
}
1.4 HHRouter
1.6k Star
1.4.1 路由准备
1.4.1.1 在AppDelegate的didFinishLaunchingWithOptions方法中进行路由注册;统一管理也是需要自己定义工具类
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//注册:参数传递直接在url中拼接,同前面的库url传参一致;没有找到对象类型参数的传参方法
[[HHRouter shared] map:@"/hnxxt/HnxxtNewsParentViewController/:key1" toControllerClass:[HnxxtNewsParentViewController class]];
return YES;
}
@end
1.4.2 路由使用
在需要跳转的地方调用
HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[[HHRouter shared] matchController:@"/hnxxt/HnxxtNewsParentViewController/param1"];
//参数会被放到vc的params中, HHRouter 中对UIViewController写了个分类,给vc加了参数params,url中的参数都会放到这个
//parmas中
targetVc.key1 = targetVc.params[key1];
//跳转和工程现行跳转一致,HHRouter只是通过路由生成UIViewController对象,后续跳转需要自己搞
[self.navigationController pushViewController: targetVc animated:YES];
1.4.3 兼容外部调用打开APP并跳转到页面(统一处理)
查看源码也是没有提供处理外部应用打开当前app的相关路由方法,所以对于外部打开app处理还是同上述一致:
在AppDelegate中的 openURL 方法统一处理
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
//url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
//将url信息处理成 HHRouter 库合适的跳转信息进行跳转处理
HnxxtNewsParentViewController *targetVc = [[HHRouter shared] matchController:@"/hnxxt/HnxxtNewsParentViewController/param1"];
targetVc.key1 = targetVc.params[key1];
[self.navigationController pushViewController: targetVc animated:YES];
return YES;
}
1.5 ALRouter
8 star
参考HHRouter实现的路由,优化了传参方式,不再通过url传参,而是通过方法传参
1.5.1 路由准备
在AppDelegate的didFinishLaunchingWithOptions方法中进行路由注册:
- 可以通过plist文件进行统一注册
- 可以直接注册,直接注册时如需统一管理还是得自己写工具类
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//注册方式1 : plist文件注册:
[ALRouter loadConfigPlist:nil];
//注册方式2: 也可以直接注册某个controller
[ALRouter regist:@"/hnxxt/HnxxtNewsParentViewController" toControllerClass:[HnxxtNewsParentViewController class]];
return YES;
}
@end
1.5.2 路由使用
//不带参数
HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController"];
//带参数 接收参数处理同HHRouter,也是实现UIViewController的分类,利用vc中的params接收参数
HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController" withParams:@{"key1":"param1"}];
targetVc.param1 = targetVc.params[key1];
//跳转同样需要自己实现
[self.navigationController pushViewController:[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController" ] animated:YES completion:nil];
1.4.3 兼容外部调用打开APP并跳转到页面(统一处理)
查看源码也是没有提供处理外部应用打开当前app的相关路由方法,所以对于外部打开app处理还是同上述一致:
在AppDelegate中的 openURL 方法统一处理
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
//url的值即为外部调用的,比如浏览器H5链接,xxt://open/HnxxtNewsParentViewController/{key:value}/push
//将url信息处理成 ALRouter 库合适的跳转信息进行跳转处理
HnxxtNewsParentViewController *targetVc = (HnxxtNewsParentViewController*)[ALRouter openURL:@"/hnxxt/HnxxtNewsParentViewController" withParams:@{"key1":"param1"}];
targetVc.param1 = targetVc.params[key1];
[self.navigationController pushViewController:targetVc animated:YES completion:nil];
return YES;
}
2. 基于runtime类型的路由
2.1 CTMediator
3.8k Star
2.1.1 路由准备
假如路由目标vc叫 HnxxtNewsParentViewController:
1、新建Target_Hnxxt类,在这个类里去写方法提供HnxxtNewsParentViewController的实例
2、创建 CTMediator 的 Category,比如叫CTMediator+Hnxxt,在这个类里提供对外调用的路由方法
这个库也不需要初始化操作;另外这里在Target_Hnxxt和CTMediator+Hnxxt中都对路由进行了统一管理;Target_Hnxxt使CTMediator和具体的路由目标类进行了解耦,详细示例如下:
//1、新建Target_Hnxxt类
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface Target_Push : NSObject
- (UIViewController *)Action_GetHnxxtNewsParentViewController:(NSDictionary *)params {
HnxxtNewsParentViewController *targetVc = [[HnxxtNewsParentViewController alloc] init];
//可以利用params,设置targetVc的参数
targetVc.param1 = params[key1];
return targetVc;
}
@end
//2、创建 CTMediator 的 Category,比如叫CTMediator+Hnxxt
#import "CTMediator+Hnxxt.h"
//kMediatorTarget字符串 是 Target_Hnxxt.h 中的 Hnxxt 部分
NSString * const kMediatorTarget = @"Hnxxt";
//kMediatorAction是 Target_Hnxxt.h 中 定义的 Action_GetHnxxtNewsParentViewController 函数名的 GetHnxxtNewsParentViewController 部分
NSString * const kMediatorAction = @"GetHnxxtNewsParentViewController";
@implementation CTMediator (Hnxxt)
- (UIViewController *)routeToHnxxtNewsParentViewController:(NSDictionary *)params{
UIViewController *viewController = [self performTarget:kMediatorTarget
action:kMediatorAction
params:params
shouldCacheTarget:NO];
if ([viewController isKindOfClass:[UIViewController class]]) {
GGLog(@"%@ 实例化页面成功", NSStringFromSelector(_cmd));
return viewController;
}else{
GGLog(@"%@ 未能实例化页面", NSStringFromSelector(_cmd));
return [[UIViewController alloc]init];
}
}
@end
2.1.2 路由使用
//在需要跳转的地方调用
NSDictionary *params = @{@"key1":@"param1"};
HnxxtNewsParentViewController * targetVc = (HnxxtNewsParentViewController*)[[CTMediator sharedInstance] routeToHnxxtNewsParentViewController:params];
//跳转时也需要自己处理
[self.navigationController pushViewController:targetVc animated:YES];
2.1.3 兼容外部调用打开APP并跳转到页面(统一处理)
在AppDelegate中的 openURL 方法进行处理,思路也是将远程的url转换为本地的跳转方式
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
//url的值即为外部调用的,格式类似:xxt://Target_Hnxxt/Action_GetHnxxtNewsParentViewController?key1=param1
//直接调用库的方法,这个方法内部也是解析这个url,然后换成本地调用
return [[[CTMediator sharedInstance] performActionWithUrl:url completion:nil] boolValue];
}
总结:
以上就是对目前iOS 比较流行的路由库的笼统介绍,期间涉及到的第三方库的使用细节,还需要大家在用到的时候再仔细对照官方文档和demo,也可以参考下面这些我总结的相关技术博客,比官方文档更贴合实际使用:
参考资料:
iOS 组件化 —— 路由设计思路分析
iOS 组件化-路由解耦思想 JLRoutes 实战篇(一)App内控制器跳转
routable-ios源码解析
iOS开发 — HHRouter路由数据传递开发分享
CTMediator: iOS应用架构谈 组件化方案
CTMediator:在现有工程中实施基于CTMediator的组件化方案
CTMediator 的初体验