前言
其实早有很多大佬,写过路由的设计方案,更有一些开源的代码例子来抛砖引玉.但愿本文也有一些闪光点,可以被当做借鉴.首先我希望读者可以按照顺序先阅读其它几篇博客,这些都是精品,如果仅仅是觉得好,收藏起来留待他日我觉得是暴殄天物了.因为我发现收藏或者喜欢的博客,往往束之高阁.
而且在吸收了大佬的思想,作为受益者,我也有义务去推广.
limboy的蘑菇街 App 的组件化之路可以说是先驱者了,先阅读这篇可以对路由的设计及作用有一点大致的理解.我认为在Load中注册的方式不可取,这会导致项目庞大后的常驻内存以及注册分散的问题.
然后读casatwy的iOS应用架构谈 组件化方案,该文阐述了蘑菇街的不足,以及Target-Action模式的优势.我认为这是一个很好的思路,主动发现服务的功能很强大.
还有在现有工程中实施基于CTMediator的组件化方案示例.
沟通总是容易有不够充分的地方,为此limboy写了蘑菇街 App 的组件化之路·续来补充阐述不足及吸收Target-Action的思想而完善Protocol/URL 注册.
当然还有其它的路由设计方案,一缕殇流化隐半边冰霜
在iOS 组件化 —— 路由设计思路分析中总结的很好,在看完我推荐的几篇博文后,这一篇阅读速度可以加快.
我的观点
JLRoutes : 降龙十八掌,内力深厚.
Routable ; 一阳指(不知道怎么描述了).
HHRouter : 缥缈剑法,灵动(动态绑定入参).
MGJRouter : 七伤拳,初练不觉,越练越伤,伤及肺腑.
CTMediator : 打狗棒法,招式新颖,变化多端.
我的实践结果
我是基于Target-Action的思想,来写自己的路由的.但是和CTMediator不同的是,我没有基于Runtime来做主动发现服务,更没有利用方法签名指定Target来执行Action.因为CTMediator不能算是路由,而是一整个组件化的方案(CTMediator的demo可以自行下载).可以明显看出下面的代码并不是路由需要做的事情.本文只写路由,负责页面跳转及一个参数保存.
TableViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 通过mediator来配置cell实例,由于target已经被cache了,高频调用不是问题。
return [[CTMediator sharedInstance] CTMediator_tableViewCellWithIdentifier:@"cell" tableView:tableView];
}
CTMediator+CTMediatorModuleAActions.m
- (UITableViewCell *)CTMediator_tableViewCellWithIdentifier:(NSString *)identifier tableView:(UITableView *)tableView
{
return [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionCell
params:@{
@"identifier":identifier,
@"tableView":tableView
}
shouldCacheTarget:YES];
}
为了方便使用者调用,使用链式语法.另外虽然ViewController是push还是present或者是其它的方式是由调用者决定的,但是我认为这个没有必要每一个地方都写同样的代码.可以把转场方式当做参数来传递,这同样是调用者决定的,所以你会看到一个非常简介的语法及调用方式
入参,回调,转场方式,动画方式,一共四个维度,16种情况,本文不一一列出了
//push
1.无入参,无回调
ShareRouter.target(SNRouterPath00001).push();
2.无入参,有回调
ShareRouter.target(SNRouterPath01001).push().back(^(id data) {
NSLog(@"%@", data);
});
3.有入参,无回调
ShareRouter.target(SNRouterPath02001)
.send(@"有入参,无回调 入参为 nice")
.push();
4.有入参,有回调
ShareRouter.target(SNRouterPath03001)
.send(@"wupeng, send data").push()
.back(^(id data) {
NSLog(@"%@", data);
});
//接收数据
//这里可以借鉴HHRouter将入参和回调与ViewController,动态绑定,这样
ShareRouter.sendData = @"有入参,有回调, 入参为 wupeng";
//回调数据
ShareRouter.backData = @"有入参,有回调, 回调为wupeng";
路由组成部分
1.RouterPath:包含所有ViewController的枚举,这个以说是手动注册,也可以说不是.因为业务需要,统一配置路由码,比如00001,前两位表示组件编号,后三位表示组件00的001页面.如果没有一个业务需求是不需要这一层的.但是我认为最好加上这一层,方便管理.而且这映射关系字典是@(RouterPath):@"TargetViewController",这样并不会有多少常驻内存问题.还有另外一个好处就是不用硬编码.比如一般的URL注册mgj://router/path这样就会造成硬编码,或者是CTMediator的Target也是硬编码.当然他们也可以将这部分抽出来自定义,统一管理.
typedef NS_ENUM(NSUInteger, SNRouterPath) {
//module00
SNRouterPath00000 = 0,
SNRouterPath00001,
SNRouterPath00002,
SNRouterPath00003,
SNRouterPath00004,
//module01
SNRouterPath01000 = 1000,
SNRouterPath01001,
SNRouterPath01002,
SNRouterPath01003,
SNRouterPath01004,
//module02
SNRouterPath02000 = 2000,
SNRouterPath02001,
SNRouterPath02002,
SNRouterPath02003,
SNRouterPath02004,
//module03
SNRouterPath03000 = 3000,
SNRouterPath03001,
SNRouterPath03002,
SNRouterPath03003,
SNRouterPath03004,
};
2.RouterPathMap:将RouterPath映射成ViewController
另外写了Router的Category,来添加映射关系,为了更加便于阅读,将每个组件隔离开来,然后通过NSDictory+Add的category来使字典相加,获取所有的map映射关系.
- (NSDictionary *)maps {
NSDictionary *module00 = [NSDictionary dictionary];
NSDictionary *module01 = [NSDictionary dictionary];
NSDictionary *module02 = [NSDictionary dictionary];
NSDictionary *module03 = [NSDictionary dictionary];
module00 = @{
@(SNRouterPath00000) : @"ViewController0",
@(SNRouterPath00001) : @"ViewController0",
@(SNRouterPath00002) : @"ViewController0",
@(SNRouterPath00003) : @"ViewController0",
@(SNRouterPath00004) : @"ViewController0",
};
module01 = @{
@(SNRouterPath01000) : @"ViewController1",
@(SNRouterPath01001) : @"ViewController1",
@(SNRouterPath01002) : @"ViewController1",
@(SNRouterPath01003) : @"ViewController1",
@(SNRouterPath01004) : @"ViewController1",
};
module02 = @{
@(SNRouterPath02000) : @"ViewController2",
@(SNRouterPath02001) : @"ViewController2",
@(SNRouterPath02002) : @"ViewController2",
@(SNRouterPath02003) : @"ViewController2",
@(SNRouterPath02004) : @"ViewController2",
};
module03 = @{
@(SNRouterPath03000) : @"ViewController3",
@(SNRouterPath03001) : @"ViewController3",
@(SNRouterPath03002) : @"ViewController3",
@(SNRouterPath03003) : @"ViewController3",
@(SNRouterPath03004) : @"ViewController3",
};
module00 = [module00 add:module01, module02, module03, nil];
return module00;
}
3.CurrentViewController:当前ViewController.这个其实是不属于Router的,但是确实这个app共有的,是任何地方的可以调用的.由于Router需要管理转场,所以使用了CurrentViewController.这个再UIWindow+Current的category中.
4.目前SNRouter是对<ReactiveObjC.h>有依赖的,因为我认为RAC的信号机制,很符合路由.但是在实践的过程中,有些用牛刀了.真正用到的也就是一个RACSubject,如果项目里面没有<ReactiveObjC.h>,仅仅是因为SNRouter就添加RAC是不值得的.为此我把RAC的订阅部分和发送部分都放到放到pushAnimated: 和 presentAnimated: 中.这样稍微改一下这两个方法变可以解除RAC的依赖. 留个小彩蛋,读者请自行尝试.
以下是SNRouter的所有参数,目前都是本地调用.远程通过URL调用,稍后我在加上.
#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>
#import "SNRouterPath.h"
#define ShareRouter [SNRouter router]
typedef void(^SendBlock)(id data);
@interface SNRouter : NSObject
@property (nonatomic, copy, readonly) SNRouter *(^ target)(SNRouterPath targetPath);
@property (nonatomic, weak) id sendData;
@property (nonatomic, copy, readonly) SNRouter *(^ send)(id data);
@property (nonatomic, weak) id backData;
@property (nonatomic, copy, readonly) SNRouter *(^ back)(SendBlock back);
@property (nonatomic, copy, readonly) SNRouter *(^ push)(void);
@property (nonatomic, copy, readonly) SNRouter *(^ pushAnimatedNO)(void);
@property (nonatomic, copy, readonly) SNRouter *(^ present)(void);
@property (nonatomic, copy, readonly) SNRouter *(^ presentAnimatedNO)(void);
+ (instancetype)router;
写作记录
1.2018.10.6开篇