提及组件化你们应该都不陌生,不过也再提一下,因为业务的复杂度扩展,各个模块之间的耦合度愈来愈高,不但形成了“牵一发动全身”的尴尬境地,还增长了测试的重复工程,此时,组件化就值得考虑了。组件化就是将APP拆分红各个组件(或者说模块),同时解除这些组件之间的耦合,而后经过路由中间件将项目所须要的组件结合起来。这样作的好处有:html
网上关于组件化的方案很多,流传最广的是蘑菇街组件化的技术方案和iOS应用架构谈 组件化方案这里不对大佬们的方案妄加评价,感兴趣的同窗能够本身看看。这里咱们聊聊另外的一种方式Protocol-Moudlegit
在iOS中,协议(Protocol)定义了一个纲领性的接口,全部类均可以选择实现。它主要是用来定义一套对象之间的通讯规则。protocol也是咱们设计时经常使用的一个东西,相对于直接继承的方式,protocol则偏向于组合模式。他使得两个绝不相关的类可以相互通讯,从而实现特定的目标。github
在以前的一篇文章ResponderChain+Strategy+MVVM实现一个优雅的TableView中咱们用到了protocol来为View提供公共的方法:bash
- (void)configCellDateByModel:(id<QFModelProtocol>)model;
架构
为Model提供公共的方法:ide
- (NSString *)identifier;
- (CGFloat)height;
复制代码
那么咱们也能够以此来构建一个轻量级的路由中间件,定义一套各个组件的通讯规则,各自管理和维护各自的模块,对外提供必要的接口。组件化
首先看一下这个Demo的结构图和运行效果
post
好了咱们看看路由的一些细节,它只须要提供两个关键的东西:测试
首先提供单例:ui
+ (instancetype)router {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_router = [[self alloc]init];
});
return _router;
}
复制代码
这样的单例可能没有人不会写,but,这其实这仅仅是个“假的”单例,不信你可使用[QFRouter router]
和[[QFRouter alloc]init]
以及[router copy]
试试他们会不会生成三个内存地址不一样的实例。可能你会说,谁会无聊这么干?可是若是设计的时候能更严谨的规避这种坑不会更好么。 那么怎么才能才能作一个严谨的单例呢?能够重写他的alloc
和copy
方法避免建立多个实例,你能够在Demo工程中看到细节,这里不作展开。
回归正题,咱们看看如何获取Module
经过Runtime的反射机制,咱们能够经过NSString获取一个class进而建立对应的对象,而Protocol又能够获得一个NSString,那么是否能够由此入手呢?答案是能够的:
- (Class)classForProtocol:(Protocol *)protocol {
NSString *classString = NSStringFromProtocol(protocol);
return NSClassFromString(classString);
}
复制代码
这里传入一个protocol便可获取对应的Module的class,再经过class便可以获得对应的Module的object。
经过Protocol或者URL获取对应的Module:
#pragma mark - Public
- (id)interfaceForProtocol:(Protocol *)protocol {
Class class = [self classForProtocol:protocol];
return [[class alloc]init];
}
- (id)interfaceForURL:(NSURL *)url {
id result = [self interfaceForProtocol:objc_getProtocol(url.scheme.UTF8String)];
NSURLComponents *cp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
[cp.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[result setValue:obj.value forKey:obj.name];//KVC设置
}];
return result;
}
复制代码
这里有个瑕疵就是调用方(外部组件)须要知道这个目标组件对外暴露的协议名称,不过既然是协议,对外公开应该不算大问题吧,调用实例以下:
wayOne:
id <MoudleHome>homeMoudle = [[QFRouter router]interfaceForProtocol:@protocol(MoudleHome)];
复制代码
这样就拿到了对应的目标组件实例,经过对外暴露的属性能够对其进行传值,经过其回调block则能够拿到回调参数。
wayTwo:
id <MoudleMe>meMoudle = [[QFRouter router]interfaceForURL:[NSURL URLWithString:@"MoudleMe://?paramterForMe=ModuleMe"]];
复制代码
这里经过url传入,经过KVC设置其属性值。一样地,经过其回调block则能够拿到回调参数。
经过上面咱们了解到:经过Protocol能够获取对应的组件实例,那么这个协议放在哪儿?如何管理呢?
在平常开发过程当中,跨组件的交互场景最多的应该就是:从组件A附带参数跳转到组件B的某个页面,组件B的这个页面中作一些操做,再回到组件A(可能有回调参数,也可能不回调参数),那么咱们的协议应该能处理这两个最多见和基础的操做,因此给protocol定义了两个属性:
typedef void(^QFCallBackBlock)(id parameter);
#pragma mark - 基础协议
@protocol QFMoudleProtocol <NSObject>
/// 暴露给组件外部的控制器,通常为该组件的主控制器
@property (nonatomic, weak) UIViewController *interfaceViewController;
/// 回调参数
@property (nonatomic, copy) QFCallBackBlock callbackBlock;
@end
复制代码
这里的interfaceViewController为什么声明成了weak属性?这个问题先留一下,后面会聊到这一点。
有了这里的两个属性咱们便可完成,对应的跳转和参数回调,可是如何正向传值呢?
应该还须要对应的属性来作入参,可是组件何其多,入参何其多,若是都把正向的属性写入这里面,那么随着时间和业务的增加,这个协议可能会十分杂乱和臃肿。
因此这里把这个协议定为基础协议,对应的组件都继承自它,而后定义各自的须要的入参属性:
首页组件:
#pragma mark - ”首页“组件
@protocol MoudleHome <QFMoudleProtocol>
/// 组件“Home”首页所须要的参数
@property (nonatomic, copy) NSString *paramterForHome;
/// 组件“Home”中详情页面所须要的参数
@property (nonatomic, copy) NSString *titleString;
/// 组件“Home”中详情页面所须要的参数
@property (nonatomic, copy) NSString *descString;
/// 组件“Home”所须要暴露的特殊接口,好比其余组件也要跳转到该页面
@property (nonatomic, weak) UIViewController *detailViewController;
@end
复制代码
能够看到,因为首页组件须要对外暴露一个主页面 QFHomeViewController
和详细页面 QFDetailViewController
因此参数会多一点。
个人组件:
#pragma mark - “个人”组件
@protocol MoudleMe <QFMoudleProtocol>
/// 组件“Me”所须要的参数
@property (nonatomic, copy) NSString *paramterForMe;
@end
复制代码
而“个人”组件,只对外提供一个QFMeViewController
页面,参数比较简单。
这样,基本算是达成了对协议的处理,可是无可避免的问题就是: 这个公共协议中定义了各个组件的协议,因此须要对多个开发团队可见,感受这也是组件化过程当中的一个广泛问题,目前没找到好的解决方式。
上面咱们说到了公开protocol中定义了一些属性,好比interfaceViewController
那么这些属性由谁提供呢?没错,就是Module,经过上面的步骤咱们能够获取到对应的Module实例,可是咱们跳转须要的是Controller,因此,在此时就须要Module的帮助了, Module经过公共协议定义的属性为外部提供Controller接口:
- (UIViewController *)interfaceViewController {
QFHomeViewController *homeViewController = [[QFHomeViewController alloc]init];
homeViewController.interface = self;
interfaceViewController = (UIViewController *)homeViewController;
return interfaceViewController;
}
复制代码
由于Module是在对应的组件中,因此能够随意引用本身组件内部的头文件完成初始化,
而对应的控制器中,须要组件外部的参数,因此这里把Module实例也暴露给对应的控制器实例,也就是homeViewController.interface = self;
所作的事情。
在上面说协议的时候咱们提到为何要使用weak,至此,应该比较明朗了 ———— 打破循环强引用。
经过公共协议解耦获取到Module,Module完成为组件内和组件外的搭桥铺路工做,由此,使得跨组件传值、调用、参数回调得以实现。更多细节请看QFMoudle。
因为时间关系,如何制做私有库就再也不赘述了,有须要欢迎留言,咱们一块儿手把手建立一个属于你本身的pod私有库。
在这种组件化的实践中,通常会把对应的组件、路由以及公共协议作成pod私有库,而须要多个团队知道的也就只有这个公共协议库,把全部的协议放入这个公开协议库中,每次升级也只须要更新这个库就行了。
关于对组件化的态度:上面说了那么多好处,下面说说弊端(亲身体会)
若是团队规模较小业务复杂度较低的话,不太建议作私有库,缘由:
任何事情都是双面的,有利有弊,望各位看官自行取舍。最后因为笔者文笔有限,若给您形成了困扰,实在抱歉,有任何疑问or意见or建议,欢迎交流讨论,固然,若是对您有用,但愿顺手给个Star,点个关注。赠人玫瑰,手留余香,您的支持是我最大的动力!