前言
随着项目中集成的系统越来越多,需求也越来越多,比如说当我们的应用程序收到一条推送消息,用户点击推送消息需要我们的应用程序直接跳转到推送此条消息的系统页面,并且携带相关的参数对这个页面进行初始化,那么我们怎么才能做到参数、页面接口可配置,并且做到iOS和Android两端只需要公用一套参数即可实现呢?带着这样的需求我们引入了routable-ios,当然它还有其他更多的优点,比如说路由式跳转,解耦等,本篇主要对routable-ios进行源码分析,探究一下它是怎么实现的。
目录
- 1.routable-ios结构
- 2.routable-ios使用示例
- 3.routable-ios源码分析
- 4.参考文章
一、routable-ios结构
routable-ios由四个类和两个文件组成,其中Routable为入口类,对routable-ios的操作都是操作的Routable,但是实际上Routable只对外提供了两个构建方法,一个单例实现,一个非单例,真正工作的是它的父类UPRouter。UPRouter中有两个可变字典,分别存储着routable-ios的另外两个类RouterParams和UPRouterOptions,其中RouterParams为存储跳转传递参数使用,UPRouterOptions为存储跳转样示和其他配置相关。
二、routable-ios使用示例
from git
Set up your app's router and URLs (usually done in application:didFinishLaunchingWithOptions:):
[[Routable sharedRouter] map:@"users/:id" toController:[UserController class]];
// Requires an instance of UINavigationController to open UIViewControllers
[[Routable sharedRouter] setNavigationController:aNavigationController];
Implement initWithRouterParams: in your UIViewController subclass:
@implementation UserController
// params will be non-nil
- (id)initWithRouterParams:(NSDictionary *)params {
if ((self = [self initWithNibName:nil bundle:nil])) {
self.userId = [params objectForKey:@"id"];
}
return self;
}
Then, anywhere else in your app, open a URL:
NSString *aUrl = @"users/4";
[[Routable sharedRouter] open:aUrl];
If you wish to do custom allocation of a controller, you can use +allocWithRouterParams:
[[Routable sharedRouter] map:@"users/:id" toController:[StoryboardController class]];
@implementation StoryboardController
+ (id)allocWithRouterParams:(NSDictionary *)params {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
StoryboardController *instance = [storyboard instantiateViewControllerWithIdentifier:@"sbController"];
instance.userId = [params objectForKey:@"id"];
return instance;
}
Set ignoresExceptions to YES to NOT throw exceptions (suggested for a Release/Distribution version)
[[Routable sharedRouter] setIgnoresExceptions:YES];
复制代码
使用起来很简单,其实就做了三件事:
1.注册路由
2.设置导航
3.进行跳转
三、routable-ios源码分析
首先探究一下这几个类基本结构:
1.Routable
//单例方法
+ (instancetype)sharedRouter;
//多例方法
+ (instancetype)newRouter;
复制代码
2.UPRouter
//导航控制器
@property (readwrite, nonatomic, strong) UINavigationController *navigationController;
- (void)pop;
- (void)popViewControllerFromRouterAnimated:(BOOL)animated;
- (void)pop:(BOOL)animated;
//控制是否需要抛出异常
@property (readwrite, nonatomic, assign) BOOL ignoresExceptions;
//注册路由
- (void)map:(NSString *)format toCallback:(RouterOpenCallback)callback;
- (void)map:(NSString *)format toCallback:(RouterOpenCallback)callback withOptions:(UPRouterOptions *)options;
- (void)map:(NSString *)format toController:(Class)controllerClass;
- (void)map:(NSString *)format toController:(Class)controllerClass withOptions:(UPRouterOptions *)options;
//路由跳转
- (void)openExternal:(NSString *)url;
- (void)open:(NSString *)url;
- (void)open:(NSString *)url animated:(BOOL)animated;
- (void)open:(NSString *)url animated:(BOOL)animated extraParams:(NSDictionary *)extraParams;
//获取给定URL的params
- (NSDictionary*)paramsOfUrl:(NSString*)url;
复制代码
3.RouterParams
//跳转方式
@property (readwrite, nonatomic, strong) UPRouterOptions *routerOptions;
//openParams根据路由规则生成的参数,extraParams手动添加的参数
@property (readwrite, nonatomic, strong) NSDictionary *openParams;
@property (readwrite, nonatomic, strong) NSDictionary *extraParams;
//controller的参数
@property (readwrite, nonatomic, strong) NSDictionary *controllerParams;
复制代码
4.UPRouterOptions
//初始化方法
+ (instancetype)routerOptionsWithPresentationStyle: (UIModalPresentationStyle)presentationStyle
transitionStyle: (UIModalTransitionStyle)transitionStyle
defaultParams: (NSDictionary *)defaultParams
isRoot: (BOOL)isRoot
isModal: (BOOL)isModal;
+ (instancetype)routerOptions;
+ (instancetype)routerOptionsAsModal;
+ (instancetype)routerOptionsWithPresentationStyle:(UIModalPresentationStyle)style;
+ (instancetype)routerOptionsWithTransitionStyle:(UIModalTransitionStyle)style;
+ (instancetype)routerOptionsForDefaultParams:(NSDictionary *)defaultParams;
+ (instancetype)routerOptionsAsRoot;
+ (instancetype)modal;
+ (instancetype)withPresentationStyle:(UIModalPresentationStyle)style;
+ (instancetype)withTransitionStyle:(UIModalTransitionStyle)style;
+ (instancetype)forDefaultParams:(NSDictionary *)defaultParams;
+ (instancetype)root;
- (UPRouterOptions *)modal;
- (UPRouterOptions *)withPresentationStyle:(UIModalPresentationStyle)style;
- (UPRouterOptions *)withTransitionStyle:(UIModalTransitionStyle)style
- (UPRouterOptions *)forDefaultParams:(NSDictionary *)defaultParams;
- (UPRouterOptions *)root;
//是否模态
@property (readwrite, nonatomic, getter=isModal) BOOL modal;
//试图显示样式
@property (readwrite, nonatomic) UIModalPresentationStyle presentationStyle;
//试图显示时的动画
@property (readwrite, nonatomic) UIModalTransitionStyle transitionStyle;
//默认传递的参数
@property (readwrite, nonatomic, strong) NSDictionary *defaultParams;
//设置为根控制器
@property (readwrite, nonatomic, assign) BOOL shouldOpenAsRootViewController;
复制代码
接下来通过源码探究下他们是怎么工作的:
1.路由注册
- (void)map:(NSString *)format toController:(Class)controllerClass withOptions:(UPRouterOptions *)options {
if (!format) {
@throw [NSException exceptionWithName:@"RouteNotProvided"
reason:@"Route #format is not initialized"
userInfo:nil];
return;
}
if (!options) {
options = [UPRouterOptions routerOptions];
}
options.openClass = controllerClass;
//1.配置UPRouterOptions存储到routes中,
//2.routes->key:url format,value:UPRouterOptions
[self.routes setObject:options forKey:format];
}
+ (instancetype)routerOptions {
return [self routerOptionsWithPresentationStyle:UIModalPresentationNone
transitionStyle:UIModalTransitionStyleCoverVertical
defaultParams:nil
isRoot:NO
isModal:NO];
}
+ (instancetype)routerOptionsWithPresentationStyle: (UIModalPresentationStyle)presentationStyle
transitionStyle: (UIModalTransitionStyle)transitionStyle
defaultParams: (NSDictionary *)defaultParams
isRoot: (BOOL)isRoot
isModal: (BOOL)isModal {
UPRouterOptions *options = [[UPRouterOptions alloc] init];
options.presentationStyle = presentationStyle;
options.transitionStyle = transitionStyle;
options.defaultParams = defaultParams;
options.shouldOpenAsRootViewController = isRoot;
options.modal = isModal;
return options;
}
复制代码
路由注册这一块没什么好讲的,主要目的是生成RouterParams对象,以format为key,RouterParams为value存储到routes字典中,用于之后遍历使用,接下来是本篇重点,那么注册好的路由,routable-ios是怎么进行跳转的,并且将相应的参数进行传递的。
路由跳转
- (void)open:(NSString *)url
animated:(BOOL)animated
extraParams:(NSDictionary *)extraParams
{
//获取参数
RouterParams *params = [self routerParamsForUrl:url extraParams: extraParams];
UPRouterOptions *options = params.routerOptions;
//是否有回调,有回调执行回调
if (options.callback) {
RouterOpenCallback callback = options.callback;
callback([params controllerParams]);
return;
}
//是否设置了navigationController
if (!self.navigationController) {
//如果没有设置并且不需要抛出异常return
if (_ignoresExceptions) {
return;
}
@throw [NSException exceptionWithName:@"NavigationControllerNotProvided"
reason:@"Router#navigationController has not beenset to a UINavigationController instance"
userInfo:nil];
}
//通过上面的params生成controller
UIViewController *controller = [self controllerForRouterParams:params];
if (self.navigationController.presentedViewController) {
[self.navigationController dismissViewControllerAnimated:animated completion:nil];
}
//是否模态方式弹出来
if ([options isModal]) {
if ([controller.class isSubclassOfClass:UINavigationController.class]) {
[self.navigationController presentViewController:controller
animated:animated
completion:nil];
}
else {
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
navigationController.modalPresentationStyle = controller.modalPresentationStyle;
navigationController.modalTransitionStyle = controller.modalTransitionStyle;
[self.navigationController presentViewController:navigationController
animated:animated
completion:nil];
}
}
else if (options.shouldOpenAsRootViewController) {
//设置为根试图
[self.navigationController setViewControllers:@[controller] animated:animated];
}
else {
[self.navigationController pushViewController:controller animated:animated];
}
}
复制代码
这里面主要做了三件事情,1.拿到我们要传递的参数。2.拿到我们想要跳转的控制器。3.进行跳转。接下来我们再通过源码分析一下参数是怎么拿到的,目标控制器又做了什么,那我们再着重分析一下第1、2步:
1.生成参数
- (RouterParams *)routerParamsForUrl:(NSString *)url extraParams: (NSDictionary *)extraParams {
//容错处理
if (!url) {
//if we wait, caching this as key would throw an exception
if (_ignoresExceptions) {
return nil;
}
@throw [NSException exceptionWithName:@"RouteNotFoundException"
reason:[NSString stringWithFormat:ROUTE_NOT_FOUND_FORMAT, url]
userInfo:nil];
}
//缓存中取,缓存下面添加的
if ([self.cachedRoutes objectForKey:url] && !extraParams) {
return [self.cachedRoutes objectForKey:url];
}
NSArray *givenParts = url.pathComponents;
NSArray *legacyParts = [url componentsSeparatedByString:@"/"];
//判断传入的url规则是否正确
if ([legacyParts count] != [givenParts count]) {
NSLog(@"Routable Warning - your URL %@ has empty path components - this will throw an error in an upcoming release", url);
givenParts = legacyParts;
}
//这个就是我们要的东西了RouterParams
__block RouterParams *openParams = nil;
//遍历上面注册的key和value
[self.routes enumerateKeysAndObjectsUsingBlock:
^(NSString *routerUrl, UPRouterOptions *routerOptions, BOOL *stop) {
NSArray *routerParts = [routerUrl pathComponents];
//farmal url和final url是否能匹配
if ([routerParts count] == [givenParts count]) {
//如果是匹配的,那么就去获取final url所携带的参数以NSDictionary的形式返回 如果匹配到了就返回停止遍历,
匹配不到为nil,会继续遍历
NSDictionary *givenParams = [self paramsForUrlComponents:givenParts routerUrlComponents:routerParts];
if (givenParams) {
//生成最后的参数RouterParams
openParams = [[RouterParams alloc] initWithRouterOptions:routerOptions openParams:givenParams extraParams: extraParams];
*stop = YES;
}
}
}];
if (!openParams) {
if (_ignoresExceptions) {
return nil;
}
@throw [NSException exceptionWithName:@"RouteNotFoundException"
reason:[NSString stringWithFormat:ROUTE_NOT_FOUND_FORMAT, url]
userInfo:nil];
}
//做了个缓存
[self.cachedRoutes setObject:openParams forKey:url];
return openParams;
}
- (NSDictionary *)paramsForUrlComponents:(NSArray *)givenUrlComponents
routerUrlComponents:(NSArray *)routerUrlComponents {
__block NSMutableDictionary *params = [NSMutableDictionary dictionary];
[routerUrlComponents enumerateObjectsUsingBlock:
^(NSString *routerComponent, NSUInteger idx, BOOL *stop) {
NSString *givenComponent = givenUrlComponents[idx];
//检查有没有:,如果有,就截取掉:,
//当然按照routable的规则,(users/:id)(users/4)
//也就是说当前open的时候传入第一个参数不是users,
//那么会直接*stop = YES,结束当前遍历,然后会去routes取下一个直到找到为止
//这样可以保证时间复杂度最大为O(1)
if ([routerComponent hasPrefix:@":"]) {//说明是参数
NSString *key = [routerComponent substringFromIndex:1];
[params setObject:givenComponent forKey:key];
}
else if (![routerComponent isEqualToString:givenComponent]) {//比对users 路由id
params = nil;
*stop = YES;
}
}];
return params;
}
复制代码
2.创建注册的controller
- (UIViewController *)controllerForRouterParams:(RouterParams *)params {
//类方法选择子
SEL CONTROLLER_CLASS_SELECTOR = sel_registerName("allocWithRouterParams:");
//对象方法选择子
SEL CONTROLLER_SELECTOR = sel_registerName("initWithRouterParams:");
UIViewController *controller = nil;
Class controllerClass = params.routerOptions.openClass;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//是否实现了类方法
if ([controllerClass respondsToSelector:CONTROLLER_CLASS_SELECTOR]) {
controller = [controllerClass performSelector:CONTROLLER_CLASS_SELECTOR withObject:[params controllerParams]];
}
//是否实现了对象方法,这里也就是我们注册的类为什么要实现
initWithRouterParams:方法了
else if ([params.routerOptions.openClass instancesRespondToSelector:CONTROLLER_SELECTOR]) {
controller = [[params.routerOptions.openClass alloc] performSelector:CONTROLLER_SELECTOR withObject:[params controllerParams]];
}
#pragma clang diagnostic pop
if (!controller) {
if (_ignoresExceptions) {
return controller;
}
@throw [NSException exceptionWithName:@"RoutableInitializerNotFound"
reason:[NSString stringWithFormat:INVALID_CONTROLLER_FORMAT, NSStringFromClass(controllerClass), NSStringFromSelector(CONTROLLER_CLASS_SELECTOR), NSStringFromSelector(CONTROLLER_SELECTOR)]
userInfo:nil];
}
controller.modalTransitionStyle = params.routerOptions.transitionStyle;
controller.modalPresentationStyle = params.routerOptions.presentationStyle;
//拿到初始化完成的controller,跳转
return controller;
}
复制代码
到这里routable-ios的所有工作就都结束了,这其中还有些配置项没有具体分析,当然每个框架都会有它的优缺点,具体还要看自己项目的需求,看是否真的符合我们的需求,不然必然会适得其反。总体来说routable-ios算是属于轻量级且实用的框架了。