一个高效、灵活、易用的 iOS URL Router
随着用户需求的不断增多,对App的用户体验要求也越来越高。为了更好地应对这些需求,开发人员从软件工程的角度出发,将App架构从简单的MVC转变为MVVM,VIPER等复杂的架构。更换适合业务的架构是为了后期更好地维护项目。
但是用户仍然不满意,继续对开发人员提出了更高要求,不仅需要高质量的用户体验,还要求快速迭代,最好一天能推出一个新功能,且用户要求在不更新App的情况下就能体验到新功能。为了满足用户需求,开发人员就采用了H5、ReactNative、Weex等技术对现有项目进行改造。项目架构也随之变得更加复杂,纵向上进行分层,如网络层、UI layer层、数据持久层。每层横向上也会根据业务进行组件化。尽管这样做提高了开发效率,也便于维护,但如何解耦各层,如何解耦各个界面和组件,如何降低组件之间的耦合度,如何使整个系统在面对复杂情况时仍能保持“高内聚、低耦合”的特点?这一系列问题都摆在了开发人员面前,亟待解决。
例如微信的3D-Touch可以直接跳转到“我的二维码”,“我的二维码”界面位于第三级界面之内。或者更进一步,如果产品需求更加变态,要求跳转到第十层界面,如何处理?
如果自己有几个App,相互间也需要相互跳转,怎么办?
随着项目越来越复杂,各组件、各页面之间的跳转逻辑关联性越来越多,如何优雅地解除这些组件和页面之间的耦合性?
项目里的某些模块会混合使用ReactNative、Weex、H5界面,这些界面还会调用Native的界面和组件。那么,如何统一Web端和Native端请求资源的方式?
如果使用了动态下发配置文件来配置App的跳转逻辑,那么如何使iOS和Android两端只用一套配置文件?
如果App出现bug了,如何不用JSPatch就能实现简单的热修复功能?
例如App上线后突然遇到了紧急bug,能否将页面动态降级为H5、ReactNative、Weex?或者直接切换到一个本地的错误界面?
如何在每个组件间的调用和页面跳转时都进行埋点统计?在每个跳转处都手动编写代码埋点?利用Runtime AOP?
如何在每个组件间的调用过程中,加入调用逻辑检查、令牌机制,配合灰度进行风控逻辑?
如何在App中的任何界面都能调用同一个界面或组件?只能在AppDelegate中注册单例来实现?
例如,当App出现问题时,用户可能在任何界面,如何能随时随地进行强制登出?或者强制跳转到同一本地的error界面?或者跳转到相应的H5、ReactNative、Weex界面?如何让用户在任何界面,随时随地点击弹出View?
实际上,上述问题都可以通过在App端设计一个路由来解决。
因此,设计一个KYRouter路由来解决这些问题。
已经有几款不错的Router,如JLRoutes、HHRouter,但仔细看过之后发现,它们还是不太满足需求。
JLRoutes的问题主要在于URL查找实现不够高效,是通过遍历而不是匹配。另外功能偏多。
HHRouter的URL查找是基于匹配的,因此会更高效,KYRouter也是采用这种方法,但它与ViewController的绑定过于紧密,一定程度上降低了灵活性。
KYRouter呢?
借鉴了JLRoutes未能匹配时自动降级到全局的思想。
如果不小心,URL的处理可能会散落在项目的各个角落,不利于管理。例如,注册时的pattern是 ky://beauty/:id,然后打开时是 KY://beauty/123,这样一旦URL发生改动,处理起来会很麻烦,不便于统一管理。
因此,KYRouter提供了一个公有类方法来解决这个问题。
#define TEMPLATE_URL @"qq://name/:name"
[KYRouter registerURLPattern:TEMPLATE_URL toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameters[name]:%@", routerParameters[@"name"]); // halfrost
}];
[KYRouter openURL:[KYRouter generateURLWithPattern:TEMPLATE_URL parameters:@[@"halfrost"]]];
}
generateURLWithPattern:
函数会对我们定义的宏中的所有:进行替换,替换成后面的字符串数组,依次赋值。
pod 'KYRouter', '~>1.0.0'
[KYRouter registerURLPattern:@"ky://foo/bar" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameterUserInfo:%@", routerParameters[KYRouterParameterUserInfo]);
}];
[KYRouter openURL:@"ky://foo/bar"];
当匹配到URL后,routerParameters
会自带一些键
extern NSString *const KYRouterParameterURL;
extern NSString *const KYRouterParameterCompletion;
extern NSString *const KYRouterParameterUserInfo;
[KYRouter registerURLPattern:@"ky://category/travel" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameters[KYRouterParameterUserInfo]:%@", routerParameters[KYRouterParameterUserInfo]);
// @{@"user_id": @1900}
}];
[KYRouter openURL:@"ky://category/travel" withUserInfo:@{@"user_id": @1900} completion:nil];
[KYRouter registerURLPattern:@"ky://search/直播" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameters:%@", routerParameters);
}];
[KYRouter openURL:@"ky://search/直播"];
[KYRouter registerURLPattern:@"ky://" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"没有人处理该 URL,就只能 fallback 到这里了");
}];
[KYRouter openURL:@"ky://search/travel/china?has_travelled=0"];
[KYRouter registerURLPattern:@"ky://detail" toHandler:^(NSDictionary *routerParameters) {
NSLog(@"匹配到了 url, 一会会执行 Completion Block");
// 模拟 push 一个 VC
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
void (^completion)() = routerParameters[KYRouterParameterCompletion];
if (completion) {
completion();
}
});
}];
[KYRouter openURL:@"ky://detail" withUserInfo:nil completion:^{
[self appendLog:@"Open 结束,我是 Completion Block"];
}];
URL的处理一不小心,就易散落在项目的各个角落,不易管理。例如注册时的模式为ky://beauty/:id
,然后在打开时就是ky://beauty/123
,这样当url有变动时,处理起来就会很麻烦,不易统一管理。
因此,KYRouter提供了一个类方法来处理这个问题。
+ (NSString *)generateURLWithPattern:(NSString *)pattern parameters:(NSArray *)parameters;
使用方式
#define TEMPLATE_URL @"ky://search/:keyword"
[KYRouter registerURLPattern:TEMPLATE_URL toHandler:^(NSDictionary *routerParameters) {
NSLog(@"routerParameters[keyword]:%@", routerParameters[@"keyword"]); // Hangzhou
}];
[KYRouter openURL:[KYRouter generateURLWithPattern:TEMPLATE_URL parameters:@[@"Hangzhou"]]];
}
这样就可以在一个地方定义所有的URL Pattern,使用时,用这个方法生成URL即可。
更多信息,请参考
demo
KYRouter许可在MIT协议下使用。查阅LICENSE文件以获取更多信息。