内容简介:公司业务不断迭代扩张,项目的功能越来越多也越来越复杂,各个业务之间也不可避免的耦合越来越多,代码也越来越臃肿,原来的模式已经无法满足现有项目开发高复用、高可维护性的需求,目前业界解决业务多样性复杂性比较好的一种架构思路就是组件化,将项目拆分成各个模块,这样能很好的解决现有的代码耦合度高、复用性不足的问题,也方便管理各个模块。前期调研了一些组件化的方案,大致归纳为三个方案url-block、protocol-class、target-action,经过权衡后,我们最终选择target-action这种方案,说
公司业务不断迭代扩张,项目的功能越来越多也越来越复杂,各个业务之间也不可避免的耦合越来越多,代码也越来越臃肿,原来的模式已经无法满足现有项目开发高复用、高可维护性的需求,目前业界解决业务多样性复杂性比较好的一种架构思路就是组件化,将项目拆分成各个模块,这样能很好的解决现有的代码耦合度高、复用性不足的问题,也方便管理各个模块。
技术选型
前期调研了一些组件化的方案,大致归纳为三个方案url-block、protocol-class、target-action,经过权衡后,我们最终选择target-action这种方案,说到这里肯定会有人问了你们是基于什么理由去定的这个方案的呢?由于三个方案的优缺点全部描述篇幅太大,下面我主要基于调用方式、传参模式给大家阐述下。
调用方式
组件之间的调用其实就是调用方调用服务方的一个过程,这里就涉及到一个问题调用方如何发现服务方的问题。
- url-block是通过url来发现服务,服务方在应用启动时候优先注册一系列url和对应的block到内存中;
- protocol-class其实是基于url-block的一种扩展补充的组件化方案,服务方也需要在应用启动的时候优先将class和protocol做一个映射存放到内存中;
- target-action基于的是runtime,不涉及到任何注册映射关系的问题。
那么问题来了,url-block、protocol-class每次启动的时候都需要注册一系列的映射关系到内存,随着项目的越来越庞大,不可避免的会消耗掉更多的内存;业务的扩展变更不可避免的会涉及到服务映射关系的维护(增删改),维护成本也会不断增加,target-action则通过runtime完全避免了类似的问题,内存开销小,维护成本低。
传参模式
组件间调用,不可避免的会涉及到参数的传递。
- url-block是基于url,这样弊端就暴露无遗了,url传递的参数限制性很强,就举个简单的列子,分享模块的分享图片问题,图片之类的参数url传递不了,再比如字典、数组等,这样url传递这些参数显然不合适也不方便;
- protocol-class正是由于url-block传参的弊端才孕育而生的扩展方法,这里虽然能解决传递复杂参数的问题,但是内存增加和难维护的问题依旧存在;
- target-action方案提出了去model化传参,因为如果传递的是对应的model,因为对应的model一般都是和对应的业务或者模块挂钩的,这样组件之间本质上还是没有独立,没有达到去耦合的目的,最终这个方案调用方和服务方之间是通过字典来进行传参,这样做具备字典传参的灵活性、多样性,也具备了url-block方案不具备传递复杂参数的能力,参数去model化和调用runtime也彻底斩断了服务方和中间件之间的依赖,真正意义上实现了组件化。
备注:本文我们会不断提到一个概念叫runtime(其主要特性是消息传递,如果消息在对象中找不到,就进行转发),如果对iOS runtime不是特别了解,可以先搜索了解下,方便后面理解文章。
target-action组件化方案
方案架构
target-action组件化方案分为两种调用方式,远程调用和本地调用。
a. 远程调用通过AppDelegate代理方法传递到当前应用,调用远程接口并在内部做一些处理,处理完成后会在远程接口内部调用本地接口,以实现本地调用为远程调用服务。
b. 本地调用由 performTarget:action:params:
方法负责,但调用方一般不直接调用此方法,会通过一个中间层Media层,Media层会提供明确参数和方法名的方法,在方法内部调用 performTarget:
方法和参数的转换。
方案思路
组件化完整的链路是调用方 => 中间件 => 服务方,这样整个调用算是完成,下面从后两者的角色来阐述下大致的一个实现思路(调用方其实很简单,字面意思大家都懂)。
1、中间件
TBJMediator(中间件)是基于CTMediator(target-action方案作者提供)的优化版本,基于CTMediator做了一些优化和容错处理。首先中间件对外(调用方)暴露明确参数类型的方法,调用performTarget发现服务方对应的Target和Action,实现本地组件间的调用,实际是通过runtime(俗称消息分发)发现服务方和服务方对应的方法。这里大家可以思考一个问题,每个业务或者模块所有的调用如果都写在这个中间件中,几十个甚至几百上千个方法,势必会对这个中间件的后期维护带来极大的麻烦(埋坑),基于这样的现实孕育而生了Category方案,根据每个服务方业务,对应创建一个TBJMediator的Category(中间件分类),这样每个业务对外暴露的接口和这些Category一一对应,但是所有对外接口都根据业务分离。
2、服务方
服务方顾名思义服务的提供方,其实Target-Action这个方案名称已经提前剧透了,每个Target就是对应服务方提供的服务类,其中的每个Action就是具体的某项服务。每个组件可以根据实际需要提供一个或者多个Target类,在Target类中声明Action方法,TBJMediator通过runtime主动发现服务。
具体实施
组件化的目的就是为了降低耦合,但是项目中不可能不存在耦合,换句话说项目中各个业务都是有一定的关联性,我们要做的就是不断降低不必要的耦合,让项目变的架构清晰明了。为了能优先完成整个组件化方案,我们将拆分的维度适当放宽,剥离各个基础组件和业务组件,并保证每个组件的独立性。
具体划分出基础组件、基础业务模块、业务模块。
- 基础组件主要包含业务完全无关的一些UI控件、UI工厂类、基础 工具 类、网络请求等;
- 基础业务模块包含分享模块、插件模块、以及基础服务模块等;
- 业务模块主要包含产品模块、用户模块等。
每个模块都基于CocoaPods进行管理,并相互保持独立,业务模块相互之间的调用也均通过中间层去调用,相互之间没有直接引用。在拆分层级过程中需要注意,上层不能对下层有依赖,下层中不能包含上层的业务逻辑,对于项目中的公共资源和代码,尽量下沉到下层中。
技术实现(产品模块为例)
调用方在某处调用 [[TBJMediator sharedInstance] performTarget:targetName action:actionName params:@{...}]
向TBJMediator发起跨组件调用,TBJMediator根据获得的target和action信息,通过objective-C的Runtime转化生成Target实例以及对应的Action,然后最终调用到目标业务。
调用方
// ViewController.h #import "TBJMediator+TBJProdModul.h" UIViewController *netLoanListVC = [TBJMediator getNetLoanListVCWithTypeId:typeId title:title]; [self.navigationController pushViewController:netLoanListVC animated:YES]; 复制代码
TBJMediator分类(中间件)
// TBJMediator+TBJProdModul.h + (UIViewController *)getNetLoanListVCWithTypeId:(NSString *)typeId title:(NSString *)title; // TBJMediator+TBJProdModul.m + (UIViewController *)getNetLoanListVCWithTypeId:(NSString *)typeId title:(NSString *)title { id typeIdArg = NilObj(typeId); id titleArg = NilObj(typeId); return [[TBJMediator sharedInstance] performTarget:@"ProdModul" action:@"getNetLoanListVC" params:@{@"typeId": typeIdArg, @"title":titleArg} shouldCacheTarget:NO]; } 复制代码
服务方
// Target_ProdModul.h - (UIViewController *)getNetLoanListVC:(NSDictionary *)params; 复制代码
上面代码中调用方只需要依赖TBJMediator+TBJProdModul,进而达到了调用某个产品列表的目的,我们解耦的目的达到了。当然我们也能观察到,现在的数据传递是通过字典,没有用model传递,这样做避免了直接依赖model,避免model暴露给所有组件,而且字典传递参数很灵活,可以传递各种想要的数据类型。当然字典传递参数也不是没有缺点,为了调用方清晰,当参数个数比较多的时候,方法会看上去比较冗长,而且还需要特别注意参数的非空判断。
总结
模块化拆分时候需要注意的点
1.合理的拆分粒度
一开始拆分的时候粒度要适中,粒度太细的话拆分很困难,俗话说拔出萝卜带出泥,先将相对粗粒度的业务独立的组件拆分出来,后续如果一个拆分完成的库仍然比较臃肿的化,说明仍然存在细化拆分的余地。
2.制定拆分计划
前期将项目组件大致梳理一遍,制定一个合理的拆分计划,制定详细的整体规划能够将一些前期不合理的依赖、不合理的维度暴露出来,提升后续拆分的效率。
3.拆分原则
在拆分层级过程中需要注意,上层不能对下层有依赖,下层中不能包含上层的业务逻辑。对于项目中的公共资源和代码,尽量下沉到下层中。
模块化后相比单项目的一些缺点
1.当然模块化虽然有很多优点,但是实际操作过程中由于CocoaPods上传私有库步骤繁琐,如果每个库都是手动去上传,就会比较费劲,还是需要一些额外的脚本配合。
2.由于涉及到打包编译顺序问题(CocoaPods维护的私有库优先编译),有些预编译宏要格外注意,不然可能编译后的代码并不是你想要的,可能编译成了测试环境或者其他测试环境的代码。
3.另外每次上线之前app打包也必须要保证每个模块必须是最新的版本,相对单项目就没有这个问题。
组件化目前也只是迈出了这一步,后期还有很多需要优化改进,也希望有更多的技术大咖能给出建议。
以上所述就是小编给大家介绍的《iOS组件化实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 组件化之路—集成组件SDK
- Android组件化入门:一步步搭建组件化架构
- Android快速开发框架,基础库,样式库,组件化,组件集成
- Android组件化方案及组件消息总线modular-event实战
- 组件化实践
- 组件化架构漫谈
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First JavaScript程序设计
[美]Eric T. Freeman、[美] Elisabeth Robson / 袁国忠 / 人民邮电出版社 / 2017-9 / 129.00 元
本书语言和版式活泼,内容讲解深入浅出,是难得的JavaScript入门书。本书内容涵盖JavaScript的基本知识以及对象、函数和浏览器文档对象模型等高阶主题。书中配备了大量有趣的实例、图示和练习,让读者轻轻松松掌握JavaScript。一起来看看 《Head First JavaScript程序设计》 这本书的介绍吧!