routable-ios原始碼解析

窗前有月光發表於2018-11-23

前言

隨著專案中整合的系統越來越多,需求也越來越多,比如說當我們的應用程式收到一條推送訊息,使用者點選推送訊息需要我們的應用程式直接跳轉到推送此條訊息的系統頁面,並且攜帶相關的引數對這個頁面進行初始化,那麼我們怎麼才能做到引數、頁面介面可配置,並且做到iOS和Android兩端只需要公用一套引數即可實現呢?帶著這樣的需求我們引入了routable-ios,當然它還有其他更多的優點,比如說路由式跳轉,解耦等,本篇主要對routable-ios進行原始碼分析,探究一下它是怎麼實現的。

目錄

  • 1.routable-ios結構
  • 2.routable-ios使用示例
  • 3.routable-ios原始碼分析
  • 4.參考文章

一、routable-ios結構

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算是屬於輕量級且實用的框架了。

四、參考文章

routable-ios 原始碼解析

iOS 元件化 —— 路由設計思路分析

來源:https://juejin.im/post/5bf7cef6f265da613f2f20f2

相關文章