内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

iOS组件化实践

2019-02-10 21:06 出处:清屏网 人气: 评论(0

公司业务不断迭代扩张,项目的功能越来越多也越来越复杂,各个业务之间也不可避免的耦合越来越多,代码也越来越臃肿,原来的模式已经无法满足现有项目开发高复用、高可维护性的需求,目前业界解决业务多样性复杂性比较好的一种架构思路就是组件化,将项目拆分成各个模块,这样能很好的解决现有的代码耦合度高、复用性不足的问题,也方便管理各个模块。

技术选型

前期调研了一些组件化的方案,大致归纳为三个方案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组件化

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号