需求来源
页面跳转,主要是Controller的跳转,都是一些小的函数,并且需要创建目标controller的对象实例。希望将跳转逻辑集中在一个地方处理;并且controller之间能够解耦,不要相互持有。
服务化思路, 将服务端SOA架构引入客户端,对于服务和微服务,如何调用?采用PC互联网的经验,URL的方式是最成熟的。
组件化思路,分解日益庞大的APP客户端,如何调用组件的功能?函数、代理、block、KVO、通知...?URL纯字符的方式,解耦更彻底一点,可能更适合一点
第三方库: routable-ios
在gitHub中输入router参数,搜索结果中star排第一的开源库1323
功能1:打开ViewController
- 设置navigationController,这个只要设置一次,一个APP只有一个navigationController
UINavigationController *nav = [[UINavigationController alloc] initWithNibName:nil bundle:nil];
[Routable sharedRouter].navigationController = nav;
- 用map注册ViewController;格式(format)是 identifier/:key1/:key2,能够支持中文。
[[Routable sharedRouter] map:@"用户页面/:参数1/:qq" toController:[UserController class]];
- 使用的地方用open函数调用;格式(format)是 identifier/value1/value,能够支持中文。注意这里没有:
[[Routable sharedRouter] open:@"用户页面/dd/值2"];
- 目标ViewController重写函数initWithRouterParams(初始化函数)或者allocWithRouterParams(从故事版或者Xib中加载)
@implementation UserController
- (id)initWithRouterParams:(NSDictionary *)params {
if ((self = [self initWithNibName:nil bundle:nil])) {
NSLog(@"%@",params);
}
return self;
}
@end
@implementation UserController
+ (id)allocWithRouterParams:(NSDictionary *)params {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
StoryboardController *instance = [storyboard instantiateViewControllerWithIdentifier:@"UserController"];
NSLog(@"%@",params);
return instance;
}
@end
map时设定的key值和open时设定的value值都在字典参数params中了
Printing description of params:
{
qq = "\U503c2";
"\U53c2\U65701" = dd;
}
功能2:调用函数
- 注册map:
[[Routable sharedRouter] map:@"invalidate/:id" toCallback:^(NSDictionary *params) {
[Cache invalidate: [params objectForKey:@"id"]]];
}];
2.调用open:
[[Routable sharedRouter] open:@"invalidate/5h1b2bs"];
功能3:打开外部链接
[[Routable sharedRouter] openExternal:@"http://www.youtube.com/watch?v=oHg5SJYRHA0"];
这部分的源码:
- (void)openExternal:(NSString *)url {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
}
简评
能够router ViewController是特色
不能支持标准的URL格式是不足之处
打开外部链接功能,聊胜于无,作用不是很大
routable-ios
Routable, an in-app URL router for iOS and Android
Routable-iOS
第三方库:MGJRouter
蘑菇街提供的开源库,根据HHRouter改写的,主要功能是函数调用。在gitHub上的star数达到了513
- 注册函数:
/**
* routerParameters 里内置的几个参数会用到上面定义的 string
*/
typedef void (^MGJRouterHandler)(NSDictionary *routerParameters);
/**
* 注册 URLPattern 对应的 Handler,在 handler 中可以初始化 VC,然后对 VC 做各种操作
*
* @param URLPattern 带上 scheme,如 mgj://beauty/:id
* @param handler 该 block 会传一个字典,包含了注册的 URL 中对应的变量。
* 假如注册的 URL 为 mgj://beauty/:id 那么,就会传一个 @{@"id": 4} 这样的字典过来
*/
+ (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler;
- 调用函数:
/**
* 打开此 URL,带上附加信息,同时当操作完成时,执行额外的代码
*
* @param URL 带 Scheme 的 URL,如 mgj://beauty/4
* @param parameters 附加参数
* @param completion URL 处理完成后的 callback,完成的判定跟具体的业务相关
*/
+ (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion;
withUserInfo其实是传一个字典过去,会添加到注册函数block的routerParameters参数中
completion在注册函数的block中执行,相当于实现了双向通信,或者说是双向调用。
URL可以满足scheme://host/path?key1=value1&key2=value2的标准形式
path中如果加:,可以传可变参数,会出现在注册函数block的routerParameters参数中
没有专门针对ViewController的处理
open函数的三个参数URL,userInfo,completion都会以固定的key值传到注册函数block的routerParameters参数中
MGJRouter
蘑菇街 App 的组件化之路
蘑菇街 App 的组件化之路·续
第三方库:CTMediator
据说是当时跟蘑菇街Router一样火的一个开源库,gitHub上的star达到了595
基本思想是将scheme://host/path?key1=value1&key2=value2的标准格式对应为native的Module,Class,SEL,parameters
scheme只是解析出来进行模块调用,用简单的字符串判断,是本模块的就往下执行,非本模块的就直接返回,啥也不干
通过系统API
- (id)performSelector:(SEL)aSelector withObject:(id)object;
进行方法调用。调用结果通过一个block回传,参数是一个字典。类名通过API
Class _Nullable NSClassFromString(NSString *aClassName)
获得方法名通过API
SEL NSSelectorFromString(NSString *aSelectorName)
得到分远程调用和本地调用两种,本地调用的实际action以native开头
远程和本地调用,本质上都是用了runtime,是一样的。提供服务的类名都以Target_开头,方法都以action开头。
不像普通的第三方库,用Pod中一句话就搞定。按照作者的意思,在真实的场景中,这里应该是三个不同repo,包括中间件、接口、实现三部分。在实际使用中,应该用最原始的源文件手动导入的方式。
Category目录在实际工程中是单独的一个repo,调用者通过依赖category这个repo来完成功能调度。一般来说是每一个业务对应一个category的repo。因此调用者需要调度哪个业务,就依赖哪个业务的category。category这个repo由对应提供服务的业务来维护。
CTMediator目录在实际工程中也是一个单独的repo,仅用于存放中间件。被每一个业务线各自维护的category repo所依赖。
DemoModule目录是实际提供服务的业务,这个在实际工程中也是一个单独的repo。这个repo不被任何人所依赖,这个repo通过target-action来提供被调度的功能,然后由category repo通过runtime调度。
CTMediator
iOS应用架构谈 组件化方案
如何选择?
routable-ios应该是最先出来的,其本意应该是将分散在APP各个地方ViewController的跳转集中起来,面向切片编程的思想。这方面是独特的。block调用的功能应该是后面顺应潮流而添加的,其处理方式也很简单粗暴,就是在其option参数中增加一个callback字段,如果有,就直接执行返回了。基本思路是callback或者ViewController,否则就是异常出错了。
HHRouter应该是参考routable-ios的实现原理,对于ViewController部分的功能进行了弱化,仅仅是取得这个对象就好了,没有跳转的具体实现。对于block部分的功能进行了增强,看起来更像标准的URL
MGJRouter是在HHRouter的基础上进一步发展。完全去除了已经沦为鸡肋的ViewController部分,继续增强block部分。以URL统一处理方法调用,不论是远程的还是本地的调用。先注册,再使用。
CTMediator是完全不同的思路。抛弃了URL统一管理的思想,用runtime代替注册,减少了一半的维护工作。不过对于类名和方法名的命名上面要注意一下。至于URL,也是做了一层封装,解析之后,仍然是统一为本地的runtime调用。iOS应用架构谈 组件化方案是作者推荐的文章,写得比较清楚。
Object-C选择CTMediator方案,管理集中的注册表是一件比较麻烦的事,用Runtime来自动完成,省心省力
模块内部还是直接使用方法调用的形式,简单直接。
ViewController可以做成router的形式。难复用,并且还经常发生变化
跟IOS8之后的动态framework结合起来,中间件可以作为一个独立的基础模块,其他的模块对外接口都做成这种router形式,具体的实现注意一下类和方法的命名。
动态页面,由后台返回需要跳转的目标页面,采用URL的模式包装
如果考虑用framework进行隔离,归集调用列表并不是强需求,所以CTMediator的Category是不需要的。这部分主要是target和action名字的hardcode,在framework对外的h文件中进行说明就可以了。
Swift选择蘑菇街方案,protocol是核心,远程调用增加URL解析的过程,runtime不适合在swift中使用
protocol分为协议本身,协议使用者,协议实现者三部分
考虑与framework结合,主程序可以调用framework,但是framework不能调用主程序,framework之间可以相互调用。这个特性需要考虑进去。
使用者需要知道协议内容,实现者也需要知道协议内容,实现者和使用者可能出现在主程序,也可能出现在framework中。从这个角度考虑,协议本身应该放在一个独立的framework中,使用者和实现者只要import一下这个framework就可以了。
每个协议都通过protocol的扩展提供默认实现。就算没有实现者,协议也能够跑起来。这部分内容和协议都放在一个模块中。这就是Object-C中“not found”部分。这是swift的语言福利,要好好使用。
协议用得最多的地方是代理模式,这是一对一的关系。所以像蘑菇街方案那样提供一个使用者和实现者的配对管理模块(Module Manager)是十分必要的。这个应该单独做一个framework,作用就像MGJRouter一样了。使用者和实现者通过这个模块互相知道对方,应该是这个模块的基本功能。
对于远程调用,URL按照framework or module://class or struct/fucntion?[key : value]这种模式编码是比较好的做法,类型全部是String。对于提供远程调用的framework,多一步解析URL的步骤,之后,都转入以Protocol为核心的本地调用模式。至于解析URL这部分功能,可以作为String的扩展工具,也可以放在一个单独的framework中作为基础功能被调用。
至于安全性,对于scheme部分作为字符串的匹配是应该做一下的。再进一步,可以对参数[key:value]部分做下加密,毕竟这部分是放在URL的query参数部分的。再进一步,可以把scheme://后面部分都做一下加解密。
** 如果考虑兼容性,MGJRouter是最有优势的,自由度最大。**
routable-ios要求“page/:id”的格式。
CTMediator要求“scheme://host/path/?[parameters]”的格式
现在项目中URL的格式是"scheme://host?[parameters]",语言是Object-C,为了考虑兼容性,改动最小,决定采用MGJRouter并且只用于需要后台动态配置的页面。
本地模块的组件化还没有开始做,目前还是采用类方法+单利进行接口封装的形式,先把复用模块先分出来再说。目前产品还需要支持iOS7,所以framework的应用也要延后。
iOS 组件化方案探索
组件化架构漫谈
iOS组件化思路-大神博客研读和思考