最近研究了一下项目的组件化,把casabanglimboy的有关组件化的博客看了一遍,学到了不少东西,对目前业界的组件化方案有了一定的了解。这些高质量的博客大致讨论了组件化的三种方案:url-blockprotocol-class(和url-controller类似)、target-action,以及应用这三种组件化方案的时机、步骤、利弊等等。

本文主要介绍一下这三种组件化方案的技术实现过程,针对不同组件化方案具体应用过程中可能出现的问题加以介绍,也针对casa批判蘑菇街的组件化方案加以自己的思考,希望对需要了解组件化的朋友有一定的帮助。

为什么需要组件化

随着公司业务的不断发展,项目的功能越来越复杂,各个业务代码耦合也越来越多,代码量也是急剧增加,传统的MVC或者MVVM架构已经无法高效的管理工程代码,因此需要用一种技术来更好地管理工程,而组件化是一种能够解决代码耦合的技术。项目经过组件化的拆分,不仅可以解决代码耦合的问题,还可以增强代码的复用性,工程的易管理性等等。

组件化的过程

之前根据蘑菇街的组件化方案,limboycasa等人做了深入的讨论,并根据各自的观点给出了方案实施的理由以及利弊关系,然后又有人改进了他们的组件化方案,我总结了一下,大致有三种,下面分别介绍各自的实现过程:

方案一、url-block

这是蘑菇街中应用的一种页面间调用的方式,通过在启动时注册组件提供的服务,把调用组件使用的url和组件提供的服务block对应起来,保存到内存中。在使用组件的服务时,通过url找到对应的block,然后获取服务。

下图是url-block的架构图:

注册:

[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"]

蘑菇街为了统一iOSAndroid的平台差异性,专门用后台来管理url,然后针对不同的平台,生成不同类型的文件,来方便使用。

使用url-block的方案的确可以组建间的解耦,但是还是存在其它明显的问题,比如:

  1. 需要在内存中维护url-block的表,组件多了可能会有内存问题
  2. url的参数传递受到限制,只能传递常规的字符串参数,无法传递非常规参数,如UIImageNSData等类型
  3. 没有区分本地调用和远程调用的情况,尤其是远程调用,会因为url参数受限,导致一些功能受限
  4. 组件本身依赖了中间件,且分散注册使的耦合较多

方案二、protocol-class

针对方案一的问题,蘑菇街又提出了另一种组件化的方案,就是通过protocol定义服务接口,组件通过实现该接口来提供接口定义的服务,具体实现就是把protocolclass做一个映射,同时在内存中保存一张映射表,使用的时候,就通过protocol找到对应的class来获取需要的服务。

下图是protocol-class的架构图:

![图2](//upload-images.jianshu.io/upload_images/1843940-1935e1be9d21b852.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240 =200)

注册:

[ModuleManager registerClass:ClassA forProtocol:ProtocolA]

调用:

[ModuleManager classForProtocol:ProtocolA]

蘑菇街的这种方案确实解决了方案一中无法传递非常规参数的问题,使得组件间的调用更为方便,但是它依然没有解决组件依赖中间件的问题、内存中维护映射表的问题、组件的分散调用的问题。设计思想和方案一类似,都是通过给组件加了一层wrapper,然后给使用者调用。

同时,另一种方案是url-controller,这是LDBusMediator的组件化方案,我认为和方案二的实现原理类似。它是通过组件实现公共协议的服务,来对外提供服务。具体就是通过单例来维护url-controller的映射关系表,根据调用者的url,以及提供的参数(字典类型,所以参数类型不受约束)来返回对应的controller来提供服务;同时,为了增强组件提供服务的多样性,又通过服务协议定义了其它的服务。整体来看,LDBusMediator解决了蘑菇街的这两种组件化方案的不足,比如:通过注册封装件connector而不是block来降低了内存占用;通过字典传递参数,解决了url参数的限制性。但是,由于使用了connector来提供服务而不是组件本身,把connector作为组件的一部分,依然有组件依赖中间件的问题。

下图是LDBusMediator的组件化架构图:

方案三、target-action

casa的方案是通过给组件包装一层wrapper来给外界提供服务,然后调用者通过依赖中间件来使用服务;其中,中间件是通过runtime来调用组件的服务,是真正意义上的解耦,也是该方案最核心的地方。具体实施过程是给组件封装一层target对象来对外提供服务,不会对原来组件造成入侵;然后,通过实现中间件的category来提供服务给调用者,这样使用者只需要依赖中间件,而组件则不需要依赖中间件。

下图是casa的组件化方案架构图:

以下代码来自casa的组件化demo

target

A组件

// TargetA.h

- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params;

CTMediator分类

// CTMediator+CTMediatorModuleAActions.h

- (UIViewController *)CTMediator_viewControllerForDetail;

// CTMediator+CTMediatorModuleAActions.m

- (UIViewController *)CTMediator_viewControllerForDetail
{
return [self performTarget:kCTMediatorTargetA action:kCTMediatorActionNativFetchDetailViewController params:@{@"key":@"value"} shouldCacheTarget:NO];
}

调用

// ViewController.h
#import "CTMediator+CTMediatorModuleAActions.h" [self presentViewController:[[CTMediator sharedInstance] CTMediator_viewControllerForDetail] animated:YES completion:nil];

从以上代码可以看出,使用者只需要依赖中间件,而中间件又不依赖组件,这是真正意义上的解耦。但是casa的这个方案有个问题就是hardcode,在中间件的category里有hardcodecasa的解释是在组件间调用时,最好是去model化,所以不可避免的引入了hardcode,并且所有的hardcode只存在于分类中。针对这个问题,有人提议,把所有的model做成组件化下沉,然后让所有的组件都可以自由的访问model,不过在我看来,这种方案虽然解决了组件间传递model的依赖问题,但是为了解决这个小问题,直接把整个model层组件化后暴露给所有组件,容易造成数据泄露,付出的代价有点大。针对这个问题,经过和网友讨论,一致觉得组件间调用时用字典传递数据,组件内调用时用model传递数据,这样即减少组件间数据对model的耦合,又方便了组件内使用model传递数据的便捷性。

组件化实施的方式

组件化可以利用git的源代码管理工具的便利性来实施,具体就是建立一个项目工程的私有化仓库,然后把各个组件的podspec上传到私有仓库,在需要用到组件时,直接从仓库里面取。

1.封装公共库和基础UI库

在具体的项目开发过程中,我们常会用到三方库和自己封装的UI库,我们可以把这些库封装成组件,然后在项目里用pod进行管理。其中,针对三方库,最好再封装一层,使我们的项目部直接依赖三方库,方便后续开发过程中的更换。

2.独立业务模块化

在开发过程中,对一些独立的模块,如:登录模块、账户模块等等,也可以封装成组件,因为这些组件是项目强依赖的,调用的频次比较多。另外,在拆分组件化的过程中,拆分的粒度要合适,尽量做到组件的独立性。同时,组件化是一个渐进的过程,不可能把一个完整的工程一下子全部组件化,要分步进行,通过不停的迭代,来最终实现项目的组件化。

3.服务接口最小化

在前两步都完成的情况下,我们可以根据组件被调用的需求来抽象出组件对外的最小化接口。这时,就可以选择具体应用哪种组件化方案来实施组件化了。

总结

组件化是项目架构层面的技术,不是所有项目都适合组件化,组件化一般针对的是大中型的项目,并且是多人开发。如果,项目比较小,开发人员比较少,确实不太适合组件化,因为这时的组件化可能带来的不是便捷,而是增加了开发的工作量。另外,组件化过程也要考虑团队的情况,总之,根据目前项目的情况作出最合适的技术选型。我一直尊崇,没有最好的技术,只有最合适的技术。


实际上我的组件化经验也不是很多,本文也是根据casalimboybang等的博客中的内容,做了简要的分析,针对文中的不足之处,还望大家指出,共同进步。(文中图片来自互联网,版权归原作者所有)

参考资料

iOS应用架构谈 组件化方案

iOS组件化实践方案-LDBusMediator炼就

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

iOS 组件化方案探索

蘑菇街 App 的组件化之路

蘑菇街 App 的组件化之路·续

iOS组件化方案的几种实现的更多相关文章

  1. iOS 组件化方案探索

    来自bang's blog http://blog.cnbang.net/tech/3080/

  2. Android彻底组件化方案实践

    本文提出的组件化方案demo已经开源,参见文章Android彻底组件化方案开源. 文末有罗辑思维"得到app"的招聘广告,欢迎各路牛人加入!! 一.模块化.组件化与插件化 项目发展 ...

  3. Android组件化方案及组件消息总线modular-event实战

    背景 组件化作为Android客户端技术的一个重要分支,近年来一直是业界积极探索和实践的方向.美团内部各个Android开发团队也在尝试和实践不同的组件化方案,并且在组件化通信框架上也有很多高质量的产 ...

  4. atitit.atiHtmlUi web组件化方案与规范v1

    atitit.atiHtmlUi web组件化方案与规范v1 1. 如何在现有html 标签基础上定义自己的组件1 2. 组件的构成与定义1 3. 组件的加载1 4. 组件css的加载2 5. 操作组 ...

  5. Android 组件化方案探索与思考

    Android 组件化方案探索与思考 组件化项目,通过gradle脚本,实现module在编译期隔离,运行期按需加载,实现组件间解耦,高效单独调试. 本项目github地址 https://githu ...

  6. android组件化方案、二维码扫码、Kotlin新闻客户端、动画特效等源码

    Android精选源码 CalendarView日历选择器 android下拉刷新动画效果代码 一个非常方便的fragment页面框架 android组件化方案源码 Zxing实现二维码条形码的扫描和 ...

  7. iOS 模块化、组件化方案探索(利用cocoapods 、git 创建私有仓库)

    来自bang's blog http://blog.cnbang.net/tech/3080/ 模块化 简单来说,模块化就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能 ...

  8. Vue.js:轻量高效的前端组件化方案(转载)

    摘要:Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统.在前端纷繁复杂的生态中,Vue.js有幸受到一定程度的关注,目前在GitHub上已经有5000+的star.本文将从各方面对Vue ...

  9. iOS组件化思路 <转>

    随着应用需求逐步迭代,应用的代码体积将会越来越大,为了更好的管理应用工程,我们开始借助CocoaPods版本管理工具对原有应用工程进行拆分.但是仅仅完成代码拆分还不足以解决业务之间的代码耦合,为了更好 ...

随机推荐

  1. C#字典转换成where条件

    where 1=1 and Dictionary[key1]=Dictionary[value1] and Dictionary[key2]=Dictionary[value3].... /// &l ...

  2. Unreal Engine 4 Radiant UI 插件入门(三)——从蓝图中调用JS

    不知道大家有没有混淆.这篇教程说的是从蓝图中调用JS的功能(以改变H5内的内容). 在安装了UE4和RadiantUI的前提下学习这篇教程.本篇教程接着上一篇教程,建议请先看上一篇. 第一步:在网页中 ...

  3. 详解连接SQL Server数据库的方法,并使用Statement接口实现对数据库的增删改操作

    总结一下,连接SQL Server数据库需要以下几个步骤: 1. 导入驱动Jar包:sqljdbc.jar 2. 加载并注册驱动程序 3. 设置连接路径 4. 加载并注册驱动 5. 连接数据库 6. ...

  4. HttpClient以json形式的参数调用http接口并对返回的json数据进行处理(可以带文件)

    1.参数的url就是被调用的地址,map是你要传的参数.参数转成json我使用的是gson方式转换的. 主要使用的jar包有httpclient-4.5.3.jar.httpcore-4.4.6.ja ...

  5. ubuntu14.04_caffe2安装

    今天F8开发者大会上,Facebook正式发布Caffe2.经过一天的折腾,终于在ubuntu14.04上成功配置caffe2,现将经验分享如下: 1.ubuntu14.04系统下caffe2所需依赖 ...

  6. Android - Fragment (一)定义

    什么是Fragment,为什么要用Fragment? Fragment,直译为碎片.是Android UI的一种. Fragment加载灵活,替换方便.定制你的UI,在不同尺寸的屏幕上创建合适的UI, ...

  7. Spring Boot 使用Redis缓存

    本文示例源码,请看这里 Spring Cache的官方文档,请看这里 缓存存储 Spring 提供了很多缓存管理器,例如: SimpleCacheManager EhCacheCacheManager ...

  8. ARM开发(3)基于STM32的矩阵键盘控制蜂鸣器

    一 矩阵键盘控制蜂鸣器原理:  1.1 本实验实现8*7矩阵键盘上按键控制蜂鸣器响.  1.2 实验思路:根据电路图原理,找出矩阵键盘行列所对应的引脚,赋予对应的按键值,然后控制蜂鸣器响.  1.3 ...

  9. 利用GPU实现大规模动画角色的渲染

    0x00 前言 我想很多开发游戏的小伙伴都希望自己的场景内能渲染越多物体越好,甚至是能同时渲染成千上万个有自己动作的游戏角色就更好了. 但不幸的是,渲染和管理大量的游戏对象是以牺牲CPU和GPU性能为 ...

  10. IoC是什么

    IoC是什么 Ioc-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传 ...