iOS开发——组件化及去Mode化方案

本文详细探讨了iOS开发中的组件化目的和实现方式,强调了组件化在提高代码质量、开发效率和团队协作中的重要性。通过示例代码介绍了组件化实现的几种方案,包括URL-block、protocol-class和target-action。同时,文章讨论了去Model化的重要性,提出通过字典传递数据以减少组件间的耦合。最后,文章列举了淘宝和滴滴的组件化架构案例,强调了组件化架构的优势和实践方法。
摘要由CSDN通过智能技术生成

1.组件化的目的是什么?
最近一两年很多人都想在项目里面搞组件化,觉得搞组件化好,却鲜有人知道组件化好在哪里?组件化的目的是什么?个人觉得组件化主要有两个目的:
1.实现项目代码的高内聚低耦合;
2.方便多人多团队开发(这就是大团队为什么那么热衷于组件化的原因,对开发的效率的提升是十分显著的)。

1499663080372188.png
2.我的项目组件化应该做到什么程度?
个人认为组件化的程度取决于项目的大小,当一个项目一个人可以轻松开发和维护时,只需要项目架构保持如下图即可:
1503560345783827.jpeg

把基础层的部分抽成组件,用cocoapods来维护,实现基础层与表现层,业务层去耦合,业务层与表现层依然耦合。
当项目变的越来越大,功能模块越来越多,需要五到十几个人维护的时候,就需要明确组织里的每个人负责的功能模块,在代码层面也需要业务层和表现层区分开来,业务层和业务层之间也要区分开来,表现层与表现层之间也要区分开来,要想做到这些,需要做很多的工作,首先要划分好功能模块,保证一个模块的表现层和业务层代码在一个文件夹;然后一个模块下的表现层和业务层逻辑要有一个明显的区分;再引入一个中间件比如 CTMediator,作者自己写的中间件基于CTMediator的 ZZRouter,中间件用cocoapods来维护;每个模块根据中间件提供外部调用的接口,本模块调用外部模块,一律使用中间件来调,实现模块间的去耦合。中型项目的理想架构应该如下图:
1503560385634435.jpg

中型项目理想架构
当一个app成为超级app需要多个团队共同协作开发的时候,对组件化的要求又更高了,这个时候需要实现模块的独立编译,以及在主项目中的静态和动态加载,同时又要保证模块间的通信,正在完善的 BeeHive就是奔着这个目标去的。

为什么要组件化?而代码在慢慢堆积起来之后,许多类之间都存在着“你离不开我,我离不开你”的情况,这就会导致开发效率低下,且容易造成代码冲突。其实说白了就是耦合度太高。这样揉成一坨对测试/编译/开发效率/后续扩展都有一些坏处

每个模块都离不开其他模块,互相依赖粘在一起成为一坨

「组件化」顾名思义就是把一个大的 App 拆成一个个小的组件,相互之间不直接引用。那如何做呢?

组件化

实现方式

理想设计图,源于微信读书照理想设计图所示,Mediator作为一个中间件起着调度各个模块的作用,那么Mediator 怎么去转发组件间调用?本文将以 JLRoutes 作为Mediator。
在使用JLRoutes之前,请配置scheme,详见 http://blog.csdn.net/u010127917/article/details/50451251
JLRoutes本质可以理解为:保存一个全局的Map,key是url,value是对应的block。这样在下面的代码中:
  • (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { return [JLRoutes routeURL:url];}

如果自己被打开:
NSURL *viewUserURL = [NSURL URLWithString:@"myapp://user/view/joeldev"];[[UIApplication sharedApplication] openURL:viewUserURL];

JLRoutes就可以遍历这个全局的map,通过url来执行对应的block。
废话不多说,直接上代码吧!appdelegate中设置好匹配规则然后根据传递过来的参数进行跳转

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // navigationPush规则 [JLRoutes addRoute:@"/NaviPush/:controller" handler:^BOOL(NSDictionary<NSString *,NSString *> * _Nonnull parameters) {// 获取当前控制器 UIViewController *currentVc = [self currentViewController]; UIViewController *v = [[NSClassFromString(parameters[@"controller"]) alloc] init]; [self paramToVc:v param:parameters]; currentVc.hidesBottomBarWhenPushed = YES; [currentVc.navigationController pushViewController:v animated:YES]; currentVc.hidesBottomBarWhenPushed = NO; return YES; }]; // StoryBoardPush规则 [JLRoutes addRoute:@"/StoryBoardPush" handler:^BOOL(NSDictionary<NSString *,NSString *> * _Nonnull parameters) { // 获取当前控制器 UIViewController *currentVc = [self currentViewController]; UIStoryboard *storyboard = [UIStoryboard storyboardWithName:parameters[@"sbname"] bundle:nil]; UIViewController *v = [storyboard instantiateViewControllerWithIdentifier:parameters[@"bundleid"]]; [self paramToVc:v param:parameters]; currentVc.hidesBottomBarWhenPushed = YES; [currentVc.navigationController pushViewController:v animated:YES]; currentVc.hidesBottomBarWhenPushed = NO; return YES; }];return YES;}

其实在这个环境下不引用任何需要跳转的控制器来进行参数传递是个麻烦的问题,所以使用runtime来进行参数的传递
-(void)paramToVc:(UIViewController *) v param:(NSDictionary<NSString *,NSString *> *)parameters{ // runtime将参数传递至需要跳转的控制器 unsigned int outCount = 0; objc_property_t * properties = class_copyPropertyList(v.class , &outCount); for (int i = 0; i < outCount; i++) { objc_property_t property = properties[i]; NSString *key = [NSString stringWithUTF8String:property_getName(property)]; NSString *param = parameters[key]; if (param != nil) { [v setValue:param forKey:key]; } }}

控制器发送跳转规则及参数
-(void)btnClick:(UIButton *) sender{ if (sender.tag == 0) { NSString *customURL = @"TESTDEMO://NaviPush/SecondViewController?userId=99999&age=18"; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]]; }else{ NSString *customURL = @"TESTDEMO://StoryBoardPush?sbname=Main&bundleid=SBVC"; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]]; }}

干货来了!!!https://github.com/sthyuhao/JLRDemo

组件化的过程之前根据蘑菇街的组件化方案,limboy和casa等人做了深入的讨论,并根据各自的观点给出了方案实施的理由以及利弊关系,然后又有人改进了他们的组件化方案,我总结了一下,大致有三种,下面分别介绍各自的实现过程:
方案一、url-block这是蘑菇街中应用的一种页面间调用的方式,通过在启动时注册组件提供的服务,把调用组件使用的url和组件提供的服务block对应起来,保存到内存中。在使用组件的服务时,通过url找到对应的block,然后获取服务。下图是url-block的架构图:

iOS组件化方案的几种实现

注册:
[MGJRouter registerURLPattern:@" mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) { NSNumber *id = routerParameters[@"id"]; // create view controller with id // push view controller}];

调用:[MGJRouter openURL:@"mgj://detail?id=404"]蘑菇街为了统一iOS和Android的平台差异性,专门用后台来管理url,然后针对不同的平台,生成不同类型的文件,来方便使用。使用url-block的方案的确可以组建间的解耦,但是还是存在其它明显的问题,比如:需要在内存中维护url-block的表,组件多了可能会有内存问题 url的参数传递受到限制,只能传递常规的字符串参数,无法传递非常规参数,如UIImage、NSData等类型 没有区分本地调用和远程调用的情况,尤其是远程调用,会因为url
参数受限,导致一些功能受限 组件本身依赖了中间件,且分散注册使的耦合较多 方案二、protocol-class针对方案一的问题,蘑菇街又提出了另一种组件化的方案,就是通过protocol定义服务接口,组件通过实现该接口来提供接口定义的服务,具体实现就是把protocol和class做一个映射,同时在内存中保存一张映射表,使用的时候,就通过protocol找到对应的class来获取需要的服务。
下图是protocol-class的架构图:

iOS组件化方案的几种实现

注册:
[ModuleManager registerClass:ClassA forProtocol:ProtocolA]

调用:
[ModuleManager classForProtocol:ProtocolA]

蘑菇街的这种方案确实解决了方案一中无法传递非常规参数的问题,使得组件间的调用更为方便,但是它依然没有解决组件依赖中间件的问题、内存中维护映射表的问题、组件的分散调用的问题。设计思想和方案一类似,都是通过给组件加了一层wrapper,然后给使用者调用。同时,另一种方案是url-controller,这是LDBusMediator的组件化方案,我认为和方案二的实现原理类似。它是通过组件实现公共协议的服务,来对外提供服务。具体就是通过单例来维护url-controller的映

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农老K

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值