iOS组件化思路-大神博客研读和思考

1、大神博客研读

随着应用需求逐步迭代,应用的代码体积将会愈来愈大,为了更好的管理应用工程,咱们开始借助CocoaPods版本管理工具对原有应用工程进行拆分。可是仅仅完成代码拆分还不足以解决业务之间的代码耦合,为了更好的让拆分出去的业务工程可以独立运行,必须进行组件拆分而且实现组件服务化。css

下面是最近在行业内几个大神的博客辩论对战,具体资料以下:html

最近在参考大神们的讨论和以前的LDBusBundle方案基础上上,提炼出了一个适合中小型应用的LDBusMediator中间件,正逐渐在项目中使用。java

博客介绍:http://www.jianshu.com/p/196f66d31543
中间件Git开源地址:https://github.com/Lede-Inc/LDBusMediator.gitandroid

(1)蘑菇街的组件化方案

文章来源:

2016.03.10 蘑菇街App的组件化之路: http://limboy.me/ios/2016/03/10/mgj-components.htmlios

为何要组件化?
  • 组件和组件之间没有明确的约束;
  • 组件单独开发、单独测试,不能揉入主项目中开发,测试也能够针对性的测试;
如何管理短链?(url跳转)
[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”]

短链如何管理?git

  1. 后台专门管理短链;平台生成所需的文件,ios平台生成h,m文件,android生成java文件,注入到项目中;
  2. 开发人员查看生成文件了解全部可用URL;
  3. 缺点:没法把参数传递也经过生成方式得到;
同步的Action调用?(服务调用)

方法一:经过url的方式github

[MGJRouter registerURLPattern:@"mgj://cart/ordercount" toObjectHandler:^id(NSDictionary *routerParamters){ // do some calculation return @42; }] NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount”]

方法二:经过protocol-class对应的方式sql

把公共协议文件统一放到PublicProtocolDomain.h中,全部业务组件只依赖这个文件;protocol只能经过类方法提供?安全

@protocol MGJCart <NSObject> + (NSInteger)orderCount; @end [ModuleManager registerClass:MGJCartImpl forProtocol:@protocol(MGJCart)] [ModuleManager classForProtocol:@protocol(MGJCart)]
组件生命周期的管理:(组件管理)

启动初始化时,实例APP中全部组件的module实例,让每一个组件的module实例执行一遍didFinishLaunchingWithOptions方法:在这方法中每一个组件注册本身的URL,使用class注册;每一个组件能够自行监控系统的通知,如UIApplicationDidBecomeActiveNotification, 对于没有系统通知消息则将此方法写入module的protocol中,依次执行实例的这些protocol方法;ruby

[[ModuleManager sharedInstance] loadModuleFromPlist:[[NSBundle mainBundle] pathForResource:@"modules" ofType:@"plist"]]; NSArray *modules = [[ModuleManager sharedInstance] allModules]; for (id<ModuleProtocol> module in modules) { if ([module respondsToSelector:_cmd]) { [module application:application didFinishLaunchingWithOptions:launchOptions]; } }
组件化版本管理的问题
  1. 版本同步问题: API接口改动升级(旧接口不存在了,不向下兼容),版本的中位号发生改变;须要全部依赖其的调用都发生改变,才能保证壳工程和主工程可以同步编译经过;
  2. pod update以后编译太长: 考虑经过framework的方式进行修改;
  3. 持续集成问题: 不能只是把podspec直接扔到private repo里完事,须要扔到主工程进行打包编译,编译经过容许提供版本升级,不经过扔回去进行处理;CI编译检查,经过以后再将版本号升级到private repo中,同时修改主工程中Podfile的版本依赖号; 但若是是其它工程呢,被多个业务工程所依赖,如何办?
蘑菇街开源组件:

MGJRouter: https://github.com/mogujie/MGJRouter.git

  1. JLRoutes 的问题主要在于查找 URL 的实现不够高效,经过遍历而不是匹配。还有就是功能偏多。
  2. HHRouter 的 URL 查找是基于匹配,因此会更高效,MGJRouter 也是采用的这种方法,但它跟 ViewController 绑定地过于紧密,必定程度上下降了灵活性。

(2)反革命的组件化方案

文章来源:
蘑菇街的方案为何很差?
  • url注册对于实施组件化是彻底没有必要的,拓展性和可维护性都下降;
  • 基于openURL的方案的话,有一个致命缺陷:很是规对象没法参与本地组件间调度;可是能够经过传递params来解决,可是这样区分了远程调用和本地调用的入口;
  • 模块内部是否仍然须要使用URL去完成调度?是没有必要的,为啥要复杂化?
反革命的组件化方案:

基于Mediator模式和Target-Action模式:

[CTMediator sharedInstance]  
openUrl:url] //call from other app with url parseUrl performTarget:action:params //call form Native Module runtime [TargetA action1], [TargetA action2] [TargetB action1], [TargetB action2]
反革命组件化方案的调用方式:

本地跨组件间调用:

[[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{…}]

远程应用调用:

openUrl + parseUrl的方式; 针对请求的路由操做,直接将Target和Action的名字封装到url中;
反革命组件化方案的好处:
  1. 将远程调用和本地调用作了拆分,并且由本地应用调用位远程应用调用提供服务;
  2. 组件仅经过Action暴露可调用接口;
  3. 组件化方案必需去Model设计:只有调用方依赖Mediator,响应方依赖是没有必要的;
  4. 调用方如何知道接收方须要哪些Key的参数,如何知道有哪些target可被调用?:在mediator中维护针对Mediator的Category,每一个category对应一个target,categroy中的方法对应Action场景;
    • category为组合模式,根据不一样的分类提供不一样的方法,每一个组件对应一个category分类;
    • 参数验证和补救入口;
    • 轻松的请求转发;
    • 统一了全部组件间调用入口;
    • param的hardcode在整个app的做用域仅仅存在于category中,跟调用宏差很少;
    • 安全保证,对url中进行native前缀验证;
    • 保证动态调度考虑;
反革命组件化方案开源Demo:

代码Git地址:https://github.com/casatwy/CTMediator.git

2、实际项目中的组件化问题

(1) 为何要组件化?

  • 解决人多(更好的协做)、需求多(更好的功能模块划分)的问题;
  • 解决项目模块间的代码耦合问题;(坚定抵制业务组件间代码直接引用)

(2)如何拆分组件?(神仙们讨论的主要是产品业务组件化的问题)

  • 基础功能组件:(相似于性能统计、Networking、Patch、网络诊断等)

    • 按功能分库,不涉及产品业务需求,跟库Library相似
    • 经过良好的接口拱上层业务组件调用;
    • 不写入产品定制逻辑,经过扩展接口完成定制;
  • 基础UI组件:(例以下拉刷新组件、iCausel相似的组件)

    • 产品内通用UI组件;(各个业务模块依赖使用,但须要保持好定制扩展的设计)
    • 公共通用UI组件;(不涉及具体产品的视觉设计, 目前较少)
  • 产品业务组件:(例如圈子、1元购、登陆、客服MM等)

    • 业务功能间相对独立,相互间没有Model共享的依赖;
    • 业务之间的页面调用只能经过UIBus进行跳转;
    • 业务之间的逻辑Action调用只能经过服务提供;

(3)组件化工程须要解决的问题?

  • 组件化页面跳转(UIBus)方案要求:

    • 可以传递普通参数(系统基础数据类型)和复杂参数(url没法负载的对象),不负责CustomModel的传递处理;
    • 可以获取url对应的controller进行TabController的动态配置;
    • 可以对controller的present方式进行定制;
    • url的注册须用代码完成,必需去中心化处理;
  • 组件服务化(ServiceBus)方案要求:

    • 可以传递普通参数和复杂参数,尽可能不使用CustomModel的传递;
    • 经过接口文件的统一基础库进行依赖;(业务开发方开发阶段只须要依赖接口文件依赖库,在主项目集成测试阶段依赖全部业务组件进行测试)
    • 接口和实现类的的对应注册须用代码完成,由中间件去控制服务实现类的生成;

(通用问题:复杂参数传递 key值的硬编码问题)

(4)组件维护问题?

3、关于组件化的思考和总结

MGJRouter+ModuleManager方案 (蘑菇街方案)
CTMediator+Target-Action方案 (反革命方案)

(1)主要解决本地业务组件之间的通讯问题

组件化主要仍是解决本地业务组件间的调用,至于跨App或者Hybrid页面经过openUrl方式调用页面和服务的方式实际上是能够拆分红两个步骤的问题:特定模块解析处理+中间件调用。跨App经过info.plist配置的scheme跳转进入,hybrid页面经过JSBridge框架跳转进入,这部分都有特定的模块去解析完成。在特定的模块中是否要调用其它业务组件的页面或者服务由特定模块自行决定,这不是组件化中间件要去完成的事情。

(2)从工程代码层面来讲,组件化就是经过中间件解决组件间头文件直接引用、依赖混乱的问题;

从实际开发来讲,组件之间最大的需求就是页面跳转,须要从组件A的pageA1页面跳转到组件B的pageB1页面,避免对组件B页面ViewController头文件的直接依赖。其次就是服务的调用,服务调用模块毫不是为了解决url跳转的问题,只是服务调用方式能够用来解决页面跳转的需求,可是没有url跳转方案成本低。因此才有了蘑菇街方案的MGJRouter和ModuleManager的class-protocol方案的区别;而反革命的方案仍然用Target-Action方案来解决页面跳转问题,成本稍大;并且url跳转和服务调用是两种不一样的组件间通讯需求,用两种不一样的方式来完成更有区分度。

(3)纯中间件只负责挂接节点的通讯问题,不该涉及挂接点具体业务的任何逻辑。

中间件若是涉及到具体的业务逻辑,势必形成中间件对业务模块的直接依赖,因此中间件只须要抽象出业务通讯的基本职责,规定好协议接口,完成调度功能便可。

而每一个挂接节点(这里指业务组件)遵循中间件的协议完成挂接工做,固然这会形成挂接节点对中间件的协议依赖;调用方一样也必须经过挂接点提供的方法将调用操做push到中间件上,而不用管具体的调用过程,这样也是挂接节点依赖中间件,业务逻辑并无直接依赖中间件。这就是以前阿里无线分享的bus总线的思路,经过这种思路即便切换或者去掉中间件,都只须要在挂接节点中进行修改就能够完成,避免了对业务逻辑代码的直接调用修改。

至于去掉中间件,应用仍然可以跑的命题? 若是没有任何代码的修改,就至关于把解藕的桥梁给拆除了,再牛逼的框架也不能知足。

  • 反革命框架的调用方直接依赖中间件提供的调用方法,拆除中间件,至少须要修改调用方法。

(4)中间件是否应该解决组件对外披露url调用和服务接口信息?

中间件解决了组件间的通讯解藕问题,势必会将组件对外提供调用的信息隐藏起来,否则就不能达到解藕通讯的目标。

蘑菇街方案的披露方法:

  • url短链在后台管理,自动生成可查看的.{h,m,java}文件,开发人员经过这个文件进行查看,代码文件跟文档功能相似;没法解决参数key、类型的问题;
  • 服务调用接口统一放到PublicProtocol.h文件上,其它全部业务组件均须要依赖这个文件;

(是否把url短链和publicProtocol文件统一放到一个repo里,其实就至关于说明文档的做用)

反革命方案的披露方法:

  • 经过依赖中间件的category(target)方式,将业务组件的全部调用都经过category的方法暴露出来;
  • 优势:解决了url参数key、类型检查的问题;经过Target-Action方式同时解决了url短链和服务调用的通讯需求,并且更加适合程序猿的风格来解决问题。

4、咱们的组件化方案

以前听阿里的组件化分享以后,本身作了一套有关Bus总线的方案,可是在具体的产品使用过程当中用起来仍是麻烦,在项目中推广起来难度仍是比较大。特别是关于组件对外披露信息的部分,到如今都没有一个好的思路,虽然反革命的方案解决了披露的问题,可是我以为扩展性和可维护性上仍是比较差。

git开源地址:https://github.com/Lede-Inc/LDBusBundle_IOS.git

最近研读几个大神的博客和讨论以后,有了一些新的思路,但愿可以继续按照bus+category的思路上去专研一下,但愿可以一个真正适合在项目里推行起来的方案。

最近在参考大神们的讨论和以前的LDBusBundle方案基础上上,提炼出了一个适合中小型应用的LDBusMediator中间件,正逐渐在项目中使用。

博客介绍:http://www.jianshu.com/p/196f66d31543
中间件Git开源地址:https://github.com/Lede-Inc/LDBusMediator.git



文/philon(简书做者) 原文连接:http://www.jianshu.com/p/afb9b52143d4 著做权归做者全部,转载请联系做者得到受权,并标注“简书做者”。
相关文章
相关标签/搜索