组件化也是一个老生常谈的话题了,本文主要说一下在组件化中,站比较重要的位置的路由设计
。
你的项目里多是直接依赖了三方的路由组件,也多是本身根据项目的实际需求私人订制了一套路由组件,下面我想经过几个呼声比较高的三方组件来聊一聊路由的设计和分析。这里不推荐说你们用哪一个好哪一个很差,只是学习他们设计思想
。就比如咱们看三方库源码,应该都是学习编程和设计的思想为主。html
随着App的需求愈来愈多,业务愈来愈复杂,为了更高效的迭代以及提升用户体验,下降维护成本,对一个更高效的框架的需求也愈来愈急切。
因此咱们可能都经历过项目的重构、组件化,根据项目的实际需求,新的框架可能须要横向,纵向不一样粒度的分层,为了之后更有效率的开发和维护。随之而来的一个问题,如何保持“高内聚,低耦合”的特色,下面就来谈谈解决这个问题的一些思路。git
列举几个平时开发中遇到的问题,或者说是需求:github
以上这些问题,均可以经过设计一个路由来解决,下面带着这些问题继续看如何实现跳转。编程
经过上面的问题,咱们但愿设计一套路由,实现App外部和内部的统一跳转,因此先说一下App外部跳转的实现。数组
在info.plist里面添加URL types - URL Schemes浏览器
而后在Safari中输入这里设置的URL Schemes就能够直接打开App安全
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
}
复制代码
经过上面这个方法就能够监听到外部App的调用,能够根据须要作一些拦截或者其余操做。bash
App也是能够直接跳转到系统设置
的。好比有些需求要求检测用户有没有开启某些系统权限,若是没有开启就弹框提示,点击弹框的按钮直接跳转到系统设置里面对应的设置界面。架构
Universal links
这个功能可使咱们的App经过http连接来启动。app
设置方式:
applinks:
开头
以上就是iOS系统中App间跳转的二种方式。
说完App间的跳转逻辑,接下来就进入重点,App内部的路由设计。
主要要解决两个问题:
综合上面所说的两个问题,咱们该如何设计一个路由呢?固然是先去看看别人造好的轮子-。-,下面会列举几个我在开发中用到过,以及参考过的轮子,有拿来主义直接使用的,也有借鉴人家思想本身封装的,总之都值得学习。
JLRoutes目前GitHub上star5.3k,应该是星最多的路由组件了,因此咱们第一个分析他的设计思路。
JLRGlobal_routeControllersMap
,这个map以scheme为key,JLRoutes为value,因此每个scheme都是惟一的。+ (instancetype)routesForScheme:(NSString *)scheme
{
JLRoutes *routesController = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
JLRGlobal_routeControllersMap = [[NSMutableDictionary alloc] init];
});
if (!JLRGlobal_routeControllersMap[scheme]) {
routesController = [[self alloc] init];
routesController.scheme = scheme;
JLRGlobal_routeControllersMap[scheme] = routesController;
}
routesController = JLRGlobal_routeControllersMap[scheme];
return routesController;
}
复制代码
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary<NSString *, id> *parameters))handlerBlock
{
NSArray <NSString *> *optionalRoutePatterns = [JLRParsingUtilities expandOptionalRoutePatternsForPattern:routePattern];
JLRRouteDefinition *route = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:routePattern priority:priority handlerBlock:handlerBlock];
if (optionalRoutePatterns.count > 0) {
// there are optional params, parse and add them
for (NSString *pattern in optionalRoutePatterns) {
JLRRouteDefinition *optionalRoute = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:pattern priority:priority handlerBlock:handlerBlock];
[self _registerRoute:optionalRoute];
[self _verboseLog:@"Automatically created optional route: %@", optionalRoute];
}
return;
}
[self _registerRoute:route];
}
复制代码
- (void)_registerRoute:(JLRRouteDefinition *)route
{
if (route.priority == 0 || self.mutableRoutes.count == 0) {
[self.mutableRoutes addObject:route];
} else {
NSUInteger index = 0;
BOOL addedRoute = NO;
// search through existing routes looking for a lower priority route than this one
for (JLRRouteDefinition *existingRoute in [self.mutableRoutes copy]) {
if (existingRoute.priority < route.priority) {
// if found, add the route after it
[self.mutableRoutes insertObject:route atIndex:index];
addedRoute = YES;
break;
}
index++;
}
// if we weren't able to find a lower priority route, this is the new lowest priority route (or same priority as self.routes.lastObject) and should just be added if (!addedRoute) { [self.mutableRoutes addObject:route]; } } [route didBecomeRegisteredForScheme:self.scheme]; } 复制代码
- (BOOL)_routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters executeRouteBlock:(BOOL)executeRouteBlock
{
if (!URL) {
return NO;
}
[self _verboseLog:@"Trying to route URL %@", URL];
BOOL didRoute = NO;
JLRRouteRequestOptions options = [self _routeRequestOptions];
JLRRouteRequest *request = [[JLRRouteRequest alloc] initWithURL:URL options:options additionalParameters:parameters];
for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
// check each route for a matching response
JLRRouteResponse *response = [route routeResponseForRequest:request];
if (!response.isMatch) {
continue;
}
[self _verboseLog:@"Successfully matched %@", route];
if (!executeRouteBlock) {
// if we shouldn't execute but it was a match, we're done now
return YES;
}
[self _verboseLog:@"Match parameters are %@", response.parameters];
// Call the handler block
didRoute = [route callHandlerBlockWithParameters:response.parameters];
if (didRoute) {
// if it was routed successfully, we're done - otherwise, continue trying to route break; } } if (!didRoute) { [self _verboseLog:@"Could not find a matching route"]; } // if we couldn't find a match and this routes controller specifies to fallback and its also not the global routes controller, then...
if (!didRoute && self.shouldFallbackToGlobalRoutes && ![self _isGlobalRoutesController]) {
[self _verboseLog:@"Falling back to global routes..."];
didRoute = [[JLRoutes globalRoutes] _routeURL:URL withParameters:parameters executeRouteBlock:executeRouteBlock];
}
// if, after everything, we did not route anything and we have an unmatched URL handler, then call it
if (!didRoute && executeRouteBlock && self.unmatchedURLHandler) {
[self _verboseLog:@"Falling back to the unmatched URL handler"];
self.unmatchedURLHandler(self, URL, parameters);
}
return didRoute;
}
复制代码
CTMediator 目前github上star 3.3k ,这个库特别的轻量级,只有一个类和一个category,一共也没几行代码,更可的是做者还在关键代码处添加了中文注释
以及比较详细的example
。
主要思想是利用Target-Action
,使用runtime
实现解耦。这种模式每一个组件之间互不依赖,可是都依赖中间件进行调度。
头文件中暴露了两个分别处理远程App和本地组件调用的方法
// 远程App调用入口
- (id _Nullable)performActionWithUrl:(NSURL * _Nullable)url completion:(void(^_Nullable)(NSDictionary * _Nullable info))completion;
// 本地组件调用入口
- (id _Nullable )performTarget:(NSString * _Nullable)targetName action:(NSString * _Nullable)actionName params:(NSDictionary * _Nullable)params shouldCacheTarget:(BOOL)shouldCacheTarget;
复制代码
对于远程App,还作了一步安全处理,最后解析完也是一样调用了本地组件处理的方法中
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
if (url == nil) {
return nil;
}
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *urlString = [url query];
for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
NSArray *elts = [param componentsSeparatedByString:@"="];
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
// 这里这么写主要是出于安全考虑,防止黑客经过远程方式调用本地模块。这里的作法足以应对绝大多数场景,若是要求更加严苛,也能够作更加复杂的安全逻辑。
NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
if ([actionName hasPrefix:@"native"]) {
return @(NO);
}
// 这个demo针对URL的路由处理很是简单,就只是取对应的target名字和method名字,但这已经足以应对绝大部份需求。若是须要拓展,能够在这个方法调用以前加入完整的路由逻辑
id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
if (completion) {
if (result) {
completion(@{@"result":result});
} else {
completion(nil);
}
}
return result;
}
复制代码
对于无响应的请求还统一作了处理
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
SEL action = NSSelectorFromString(@"Action_response:");
NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"originParams"] = originParams;
params[@"targetString"] = targetString;
params[@"selectorString"] = selectorString;
[self safePerformAction:action target:target params:params];
}
复制代码
具体使用须要下面几个步骤 :
Target
类,以Target_
为前缀命名
Action_
为前缀命名。performTarget: action: params: shouldCacheTarget:
感兴趣的还能够看一下做者的文章,详细介绍了CTMediator的设计思想以及为已有项目添加CTMediator
iOS应用架构谈 组件化方案
在现有工程中实施基于CTMediator的组件化方案
MGJRouter 目前github上star 2.2k
这个库的由来:JLRoutes 的问题主要在于查找 URL 的实现不够高效,经过遍历而不是匹配。还有就是功能偏多。 HHRouter 的 URL 查找是基于匹配,因此会更高效,MGJRouter 也是采用的这种方法,但它跟 ViewController 绑定地过于紧密,必定程度上下降了灵活性。 因而就有了 MGJRouter。
/**
* 保存了全部已注册的 URL
* 结构相似 @{@"beauty": @{@":id": {@"_", [block copy]}}}
*/
@property (nonatomic) NSMutableDictionary *routes;
复制代码
MGJRouter是一个单例对象,在其内部维护着一个“URL -> block”格式的注册表,经过这个注册表来保存服务方注册的block。使调用方能够经过URL映射出block,并经过MGJRouter对服务方发起调用。
大概的使用流程以下:
PublicHeader
,在PublicHeader
中声明外部能够调用的一系列URL#ifndef MMCUserUrlDefines_h
#define MMCUserUrlDefines_h
/**
description 个人我的中心页
@return MMCUserViewController
*/
#define MMCRouterGetUserViewController @"MMC://User/UserCenter"
/**
description 个人消息列表
@return MMCMessageListViewController
*/
#define MMCRouterGetMessageVC @"MMC://User/MMCMessageListViewController"
复制代码
+ (void)registerGotoUserVC
{
[MMCRouter registerURLPattern:MMCRouterGetUserViewController toHandler:^(NSDictionary *params) {
}];
}
复制代码
[MMCRouter openURL:MMCRouterGetUserViewController];
复制代码
MGJRouter
还提供了能够返回一个对象的方法+ (void)registerURLPattern:(NSString *)URLPattern toObjectHandler:(MGJRouterObjectHandler)handler
复制代码
举个例子:这个route就返回了一个控制器,能够交给调用方自行处理。
+(void)registerSearchCarListVC{
[MMCRouter registerURLPattern:MMCRouterGetSearchCarListController toObjectHandler:^id(NSDictionary *params) {
NSDictionary *userInfo = [params objectForKey:MMCRouterParameterUserInfo];
NSString *carType = [userInfo objectForKey:MMCRouterCarType];
MMCSearchCarListViewController *vc = [[MMCSearchCarListViewController alloc] init];
vc.strCarType = carType;
return vc;
}];
}
复制代码
根据上面介绍的MGJRouter
的使用,不难看出存在URL硬编码和参数局限性的问题,为了解决这些问题,蘑菇街又提出来Protocol
方案。Protocol方案由两部分组成,进行组件间通讯的ModuleManager
类以及MGJComponentProtocol
协议类。
经过中间件ModuleManager
进行消息的调用转发,在ModuleManager内部维护一张映射表,映射表由以前的"URL -> block
"变成"Protocol -> Class
"。
由于目前手里的项目没有用到这个,因此使用代码就不贴了,感兴趣的能够自行百度。
优势:
缺点:
优势:
缺点:
优势:
缺点:
Target_ ,Action_
命名规则最后想说的是,没有最好的route,只有最适合你项目的route,根据本身项目的实际状况,分析决定要使用哪种组件化方案。