内容简介:公司业务不断迭代扩张,项目的功能越来越多也越来越复杂,各个业务之间也不可避免的耦合越来越多,代码也越来越臃肿,原来的模式已经无法满足现有项目开发高复用、高可维护性的需求,目前业界解决业务多样性复杂性比较好的一种架构思路就是组件化,将项目拆分成各个模块,这样能很好的解决现有的代码耦合度高、复用性不足的问题,也方便管理各个模块。前期调研了一些组件化的方案,大致归纳为三个方案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实战
- 组件化实践
- 组件化架构漫谈
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms + Data Structures = Programs
Niklaus Wirth / Prentice Hall / 1975-11-11 / GBP 84.95
It might seem completely dated with all its examples written in the now outmoded Pascal programming language (well, unless you are one of those Delphi zealot trying to resist to the Java/.NET dominanc......一起来看看 《Algorithms + Data Structures = Programs》 这本书的介绍吧!