iOS组件化方案(二)

概述

这是iOS组件化方案-总结的第二篇,在本文中我实现了Target-Action方案的Demo,并与第一篇介绍的protocol方案做出对比。

如果没有看过我第一篇protocol组件化方案的同学,可以先去下载我那篇文章中提供的Demo,方便理解我本文的详述以及了解我Demo中实现的业务场景,传送门iOS组件化方案-总结的第一篇

Target-Action方案

国际惯例先上Demo(下载主工程就好了哈,如果不能理解可以把所有业务模块都下载下来,Casa也提供了官方Demo,我第一篇文章中提供了传送门)

以下链接可到原文查看。

  • Target-Action方案主工程
  • Target-Action方案商品详情业务Category组件地址
  • Target-Action方案商品详情业务模块地址
  • Target-Action方案确认订单Category组件地址
  • Target-Action方案确认订单业务模块地址
  • Target-Action方案CTMediator地址因Casa没有把CTMediator做成公有库,所以我是直接拷贝过来做成我的私有库了。

实施

如何把模块做成私有pods我这里就不介绍了,想知道的可以看我第一篇组件化介绍文章。我这里只拿确认订单模块举例

确认订单模块是个单独的project,为了避免其他模块调用确认订单模块需引入整个模块,这里又做了一个确认订单业务Category的私有组件如下图

TAConfirmOrderBusinessCategory即是确认订单模块对外提供服务的入口,我们的业务场景是商品详情模块立即购买进入确认订单模块,确认订单模块提交订单后返回商品详情模块,同时得到通知下单成功,所以上图中入参提供了ConfirmComplete的Block,下图是TAConfirmOrderBusinessCategory.m中的实现


  1. #import "CTMediator+TAConfirmOrder.h" 
  2.   
  3. @implementation CTMediator (TAConfirmOrder) 
  4.   
  5. - (UIViewController *)confirmOrderViewControllerWithGoodsId:(NSString *)goodsId goodsName:(NSString *)goodsName ConfirmComplete:(dispatch_block_t)confirmComplete 
  6.     NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; 
  7.     params[@"goodsId"] = goodsId; 
  8.     params[@"goodsName"] = goodsName; 
  9.     params[@"completeBlock"] = confirmComplete; 
  10.     return [self performTarget:@"TAConfirmOrder" action:@"ConfirmOrderViewController" params:params shouldCacheTarget:NO]; 
  11.   
  12. @end  

OK,TAConfirmOrderBusinessCategory实现完了,我们来看下TAConfirmOrder模块,模块中定义一个Target_TAConfirmOrder具体实现如下图


  1. @interface Target_TAConfirmOrder : NSObject 
  2.   
  3. - (UIViewController *)Action_ConfirmOrderViewController:(NSDictionary *)params; 
  4.   
  5. @end 
  6.  
  7. @implementation Target_TAConfirmOrder 
  8.   
  9. - (UIViewController *)Action_ConfirmOrderViewController:(NSDictionary *)params 
  10.     TAConfirmOrderViewController *confirmOrderVC = [[TAConfirmOrderViewController alloc] init]; 
  11.     confirmOrderVC.goodsId = params[@"goodsId"]; 
  12.     confirmOrderVC.goodsName = params[@"goodsName"]; 
  13.     confirmOrderVC.confirmComplete = params[@"completeBlock"]; 
  14.     return confirmOrderVC; 
  15.   
  16. @end  

既然TAConfirmOrderBusinessCategory和TAConfirmOrder是2个project,那category是如何调用到Target_TAConfirmOrder的呢?其实很简单,我想看这篇文章的人大部分都知道,无非就是NSClassFromString ,performSelector这些方法,不知道的可以阅读源码

到这里我都没有贴过架构图或者讲过原理,只是贴了一部分代码和讲述如何实现,为什么?其实组件化原理很简单,简单到比当初学习UITableView容易多了,我的Demo即原理,如果还是看不明白可以自行google一轮或者在评论区提问.

Target_Action VS Protocol方案

1.是否需要注册?

  • Target_Action方案不需要注册
  • Protocol方案需要在启动的时候向CRProtocolManager注册

Target_Action很好的利用了runtime特性,减少注册这一步,不过对于即将切到Swift的同学就有点尴尬了。

在上篇提供的Protocol方案Demo中,在向CRProtocolManager注册服务的是实例对象而非Class,这样确实会造成内存常驻,但是无伤大雅,熟悉runtime的同学应该都知道第一次调用某个类或对象的方法,会构建出类对象,所以无论你用Class注册还是实例对象注册类对象都在,抛开类对象对于一个不挂任何property的实例对象所占用的内存是很小的。当然你可能会问既然都差不多你为什么注册实例而不是注册Class,注册的ServiceProvider实例对象在有些情况下可以记录一些状态,当然这只是极少数情况下出现的,你如果真要把ServiceProvider当单例对象用,我还是强烈建议注册Class

不过我不认为ServiceProvider需要向中间件注册有逻辑上的问题,区别只是可省可不省

2.依赖关系

Target_Action方案中商品详情模块依赖TAConfirmOrderBusinessCategory组件来获取确认订单模块的服务

Protocol方案中商品详情模块需要依赖CRConfirmOrderServiceProtocol通过CRProtocolManager组件获取提供服务的实例对象,同时确认订单模块也依赖CRConfirmOrderServiceProtocol来注册服务

乍一看Protocol方案依赖关系好像对业务产生了侵入因为调用方和实现方都同时依赖了CRConfirmOrderServiceProtocol,其实CRConfirmOrderServiceProtocol应当属于确认订单模块的一部分,把他独立出来只是为了避免调用方需要直接引用实现方,这个依赖在架构图中体现的应该是虚线而不是实线。试想如果Target_Action方案不用runtime,那BusinessCategory也需要直接依赖Target。利用runtime中NSProtocolFromString也可以解决对CRConfirmOrderServiceProtocol的依赖,只是造成一定量的硬编码不够优雅。(提一下,虽然runtime在一些特定场景给我们开发带来一些意想不到的奇效,但是runtime跳过了编译器检查,有时候排除bug比较艰难,所以还是慎用)

另Protocol方案中商品详情模块同时依赖CRConfirmOrderServiceProtocol和CRProtocolManager而Target_Action方案中商品详情模块仅依赖TAConfirmOrderBusinessCategory,依赖关系如下图

Protocol方案横向依赖了2者,Target_Action方案纵向依赖。Target_Action设计的更优异

3.可读性、硬编码

Target_Action在Category中将常规参数打包成字典,在Target处再把字典拆包成常规参数,这造成了一定量的硬编码,不过在现实开发中,一个模块一个模块提供的category通常是一个人写的,所以造成的影响微乎其微,但是给其他阅读代码的人带来一些不便,甚至同一个人写Cagetory、Target的时候也需要在2个project不停切换查看之前在Target中定义的函数名

Protocol方案中0硬编码,可读性更高。

在这里提一下Url注册方案,Url注册方案我觉得最大的问题是大量的硬编码,可读性很差,维护性也很差,对文档的依赖度很高,而且需要有人不停督促文档的更新。我想很多同学对此都深有体会,每个项目第一版的接口文档相对比较详细、全面,随着版本迭代更新,某个接口增加了一些字段,通常后台开发人员都是忘记去更新文档也许是因为忙,甚至有些同学懒的更新文档,一般这时候都是通过qq或者其他通讯工具告知客户端开发人员增加了哪些个字段,字段含义是什么。待时间长了,客户端开发人员忘记字段含义或者换了另一个开发人员接手,不知道这个字段含义是什么,先去翻看以前聊天记录,找不到去看接口文档,文档还是1.0版本。。。。我去。。。

总结

综合以上3点,Target_Action更优,我们公司目前也采用的Target_Action方案,如果有同学有计划切到Swift语言开发,我建议Protocol方案。事实上没有哪个方案是万能的,具体的采用还得结合自己的业务以及开发人员的整体素质,如果你还是拿不定主意,阿里开源了一个模块解耦框架BeeHive(protocol注册),你就向着大厂靠拢吧。Url注册方案Demo我就不提供了,因为它的可读性,维护性以及常规参数传递的缺点让我放弃了它,不过Url注册方案配合服务器下发路由能够很好的解决bug,前提是你们的模块得Native开发一套 H5开发一套(或者RN和Weex)

补 业务模块的划分

有不少同学知道了组件化,但是不知道如何去划分业务模块,我大致拿京东App某几个业务举例见下图

图片每个Module组件化后就是一个单独的project,也许很多project里面只有一个ViewController,这也是合理的划分,比如商品详情,很多模块(服装城,京东超市,全球购。。。)会调用到商品详情模块,那把商品详情模块中的业务强行塞到(服装城,京东超市,全球购。。。)任何一个project都是不合理的,确认订单同理。组件化是把业务纵切,具体到某个业务模块中network模块,database模块的划分是横切

预告

发现很多同学理解MVC的姿势不对,导致controller很臃肿难以维护,下一篇我会把自己理解的MVC写成一个Demo,这个Demo会是一个业务比较庞大的模块(所以时间会长一点。毕竟我白天要忙公司的项目,还有几个个人项目需要维护)在这个Demo中职责划分会很清楚,敬请期待哈。

本文作者:佚名

来源:51CTO

时间: 2024-12-21 11:08:37

iOS组件化方案(二)的相关文章

Android彻底组件化方案实践

本文讲的是Android彻底组件化方案实践,一.模块化.组件化与插件化 项目发展到一定程度,随着人员的增多,代码越来越臃肿,这时候就必须进行模块化的拆分.在我看来,模块化是一种指导理念,其核心思想就是分而治之.降低耦合.而在 Android 工程中如何实施,目前有两种途径,也是两大流派,一个是组件化,一个是插件化. 提起组件化和插件化的区别,有一个很形象的图: 上面的图看上去比较清晰,其实容易导致一些误解,有下面几个小问题,图中可能说的不太清楚: 组件化是一个整体吗?去了头和胳膊还能存在吗?左图

《iOS组件与框架——iOS SDK高级特性剖析》——第6章,第6.2节打造播放引擎

6.2 打造播放引擎 iOS组件与框架--iOS SDK高级特性剖析 如果对播放控制没有深刻认识,获取音频数据将毫无意义.要在应用中播放音乐,需要创建一个MPMusicPlayerController实例.在头文件ICFViewController.h中,声明了一个名为player的MPMusicPlayerController变量,在整个示例应用中,都将使用它来控制播放以及获取当前播放的曲目的信息. 在方法viewDidLoad中,初始化了MPMusicPlayerController变量pl

Bootstrap组件系列之福利篇几款好用的组件(推荐二)_javascript技巧

在上篇文章给大家介绍了Bootstrap组件系列之福利篇几款好用的组件(推荐),接下来本文给大家介绍Bootstrap组件系列之福利篇几款好用的组件(推荐二),感兴趣的朋友一起学习吧!  七.多值输入组件manifest 关于文本框的多值输入,一直是一个比较常见的需求,今天博主推荐一款好用的多值输入组件给大家,不要谢我,请叫我"红领巾"! 1.效果展示 本地多值输入框 远程多值输入框 2.源码说明 感谢开源社区,感谢那些喜欢分享的可爱的人儿.开源地址. 3.代码示例 (1)本地多值输入

iOS开发UINavigation系列二——UINavigationItem

iOS开发UINavigation系列二--UINavigationItem 一.引言         UINavigationItem是导航栏上用于管理导航项的类,在上一篇博客中,我们知道导航栏是通过push与pop的堆栈操作来对item进行管理的,同样,每一个Item自身也有许多属性可供我们进行自定制.这篇博客,主要讨论UINavigationItem的使用方法. UINavigationBar:http://my.oschina.net/u/2340880/blog/527706. 二.来

《iOS组件与框架——iOS SDK高级特性剖析》——第2章,第2.4节地图注释和覆盖层

2.4 地图注释和覆盖层 iOS组件与框架--iOS SDK高级特性剖析 地图视图(MKMapView)是一种可滚动的视图,行为独特:以标准方式在其中添加子视图时,子视图不会随地图视图滚动,而是静止的,其相对于地图视图框架的位置始终不变.对悬停按钮或标签来说,这种特点也许不错,但在地图上标出点和细节至关重要.要标出地图视图中感兴趣的点或区域,可使用地图注释和覆盖层.地图滚动或缩放时,注释和覆盖层在地图上的位置保持不变.地图注释是使用地图上的单个坐标点定义的,而地图覆盖层可以是线段.多边形或复杂形

iOS动画开发之二——UIView动画执行的另一种方式

iOS动画开发之二--UIView动画执行的另一种方式         上一篇博客中介绍了UIView的一些常用动画,通过block块,我们可以很方便简洁的创建出动画效果:http://my.oschina.net/u/2340880/blog/484457,这篇博客再介绍一种更加传统的执行UIView的动画的方法.         这种方式相比如block的方式,显得要麻烦一些,apple官方也推荐我们使用带block的创建动画的方式,我们可以将编程重心更多的放在动画逻辑的实现上.使用begi

iOS网络编程之二——NSURLSession的简单使用

iOS网络编程之二--NSURLSession的简单使用 一.NSURLSession简介     在iOS7之后,NSURLSession作为系统推荐使用的HTTP请求框架,在进行前台请求的情况下,NSURLSession与NSURLConnection并无太大差异,对于后台的请求,NSURLSession更加灵活的优势就将展现无遗.         1.NSURLSession集合的类型         NSURLSession类提供3中Session类型:         Default类

iOS流布局UICollectionView系列二——UICollectionView的代理方法

iOS流布局UICollectionView系列二--UICollectionView的代理方法 一.引言         在上一篇博客中,介绍了最基本的UICollectionView的使用和其中我们常用的属性和方法,也介绍了瀑布流布局的过程与思路,这篇博客是上一篇的补充,来讨论关于UICollectionView的代理方法的使用.博客地址: UICollectionView的简介和简单使用:http://my.oschina.net/u/2340880/blog/522613 二.UICol

iOS界面布局之二——初识autolayout布局模型

iOS界面布局之二--初识autolayout布局模型 一.引言      在上一篇博客中介绍了传统的布局方式:autoresizing.随着iphone型号的越来越多,屏幕的标准也更加多样化,通过autoresizing已经不能满足开发的需求,而进行两套布局或者动态代码控制又大大增加了开发者的工作量,autolayout的出现拯救个这一切,它让动态布局变的十分简单便捷.     autoresizing介绍:http://my.oschina.net/u/2340880/blog/423357