最近研究了一下项目的组件化,把casa
、bang
、limboy
的有关组件化的博客看了一遍,学到了很多东西,对目前业界的组件化方案有了必定的了解。这些高质量的博客大体讨论了组件化的三种方案:url-block
、protocol-class
(和url-controller
相似)、target-action
,以及应用这三种组件化方案的时机、步骤、利弊等等。html
本文主要介绍一下这三种组件化方案的技术实现过程,针对不一样组件化方案具体应用过程当中可能出现的问题加以介绍,也针对casa
批判蘑菇街的组件化方案加以本身的思考,但愿对须要了解组件化的朋友有必定的帮助。git
随着公司业务的不断发展,项目的功能愈来愈复杂,各个业务代码耦合也愈来愈多,代码量也是急剧增长,传统的MVC
或者MVVM
架构已经没法高效的管理工程代码,所以须要用一种技术来更好地管理工程,而组件化是一种可以解决代码耦合的技术。项目通过组件化的拆分,不只能够解决代码耦合的问题,还能够加强代码的复用性,工程的易管理性等等。github
以前根据蘑菇街的组件化方案,limboy
和casa
等人作了深刻的讨论,并根据各自的观点给出了方案实施的理由以及利弊关系,而后又有人改进了他们的组件化方案,我总结了一下,大体有三种,下面分别介绍各自的实现过程:架构
这是蘑菇街中应用的一种页面间调用的方式,经过在启动时注册组件提供的服务,把调用组件使用的url
和组件提供的服务block
对应起来,保存到内存中。在使用组件的服务时,经过url
找到对应的block
,而后获取服务。app
下图是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"]
蘑菇街为了统一iOS
和Android
的平台差别性,专门用后台来管理url
,而后针对不一样的平台,生成不一样类型的文件,来方便使用。ui
使用url-block
的方案的确能够组建间的解耦,可是仍是存在其它明显的问题,好比:url
url-block
的表,组件多了可能会有内存问题url
的参数传递受到限制,只能传递常规的字符串参数,没法传递很是规参数,如UIImage
、NSData
等类型url
参数受限,致使一些功能受限针对方案一的问题,蘑菇街又提出了另外一种组件化的方案,就是经过protocol
定义服务接口,组件经过实现该接口来提供接口定义的服务,具体实现就是把protocol
和class
作一个映射,同时在内存中保存一张映射表,使用的时候,就经过protocol
找到对应的class
来获取须要的服务。
下图是protocol-class
的架构图:
注册:
[ModuleManager registerClass:ClassA forProtocol:ProtocolA]
调用:
[ModuleManager classForProtocol:ProtocolA]
蘑菇街的这种方案确实解决了方案一中没法传递很是规参数的问题,使得组件间的调用更为方便,可是它依然没有解决组件依赖中间件的问题、内存中维护映射表的问题、组件的分散调用的问题。设计思想和方案一相似,都是经过给组件加了一层wrapper
,而后给使用者调用。
同时,另外一种方案是url-controller
,这是LDBusMediator的组件化方案,我认为和方案二的实现原理相似。它是经过组件实现公共协议的服务,来对外提供服务。具体就是经过单例来维护url-controller
的映射关系表,根据调用者的url
,以及提供的参数(字典类型,因此参数类型不受约束)来返回对应的controller
来提供服务;同时,为了加强组件提供服务的多样性,又经过服务协议定义了其它的服务。总体来看,LDBusMediator解决了蘑菇街的这两种组件化方案的不足,好比:经过注册封装件connector
而不是block
来下降了内存占用;经过字典传递参数,解决了url
参数的限制性。可是,因为使用了connector
来提供服务而不是组件自己,把connector
做为组件的一部分,依然有组件依赖中间件的问题。
下图是LDBusMediator的组件化架构图:
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
里有hardcode
,casa
的解释是在组件间调用时,最好是去model
化,因此不可避免的引入了hardcode
,而且全部的hardcode
只存在于分类中。针对这个问题,有人提议,把全部的model
作成组件化下沉,而后让全部的组件均可以自由的访问model
,不过在我看来,这种方案虽然解决了组件间传递model
的依赖问题,可是为了解决这个小问题,直接把整个model
层组件化后暴露给全部组件,容易形成数据泄露,付出的代价有点大。针对这个问题,通过和网友讨论,一致以为组件间调用时用字典传递数据,组件内调用时用model
传递数据,这样即减小组件间数据对model
的耦合,又方便了组件内使用model
传递数据的便捷性。
组件化能够利用git
的源代码管理工具的便利性来实施,具体就是创建一个项目工程的私有化仓库,而后把各个组件的podspec
上传到私有仓库,在须要用到组件时,直接从仓库里面取。
在具体的项目开发过程当中,咱们常会用到三方库和本身封装的UI
库,咱们能够把这些库封装成组件,而后在项目里用pod
进行管理。其中,针对三方库,最好再封装一层,使咱们的项目部直接依赖三方库,方便后续开发过程当中的更换。
在开发过程当中,对一些独立的模块,如:登陆模块、帐户模块等等,也能够封装成组件,由于这些组件是项目强依赖的,调用的频次比较多。另外,在拆分组件化的过程当中,拆分的粒度要合适,尽可能作到组件的独立性。同时,组件化是一个渐进的过程,不可能把一个完整的工程一会儿所有组件化,要分步进行,经过不停的迭代,来最终实现项目的组件化。
在前两步都完成的状况下,咱们能够根据组件被调用的需求来抽象出组件对外的最小化接口。这时,就能够选择具体应用哪一种组件化方案来实施组件化了。
组件化是项目架构层面的技术,不是全部项目都适合组件化,组件化通常针对的是大中型的项目,而且是多人开发。若是,项目比较小,开发人员比较少,确实不太适合组件化,由于这时的组件化可能带来的不是便捷,而是增长了开发的工做量。另外,组件化过程也要考虑团队的状况,总之,根据目前项目的状况做出最合适的技术选型。我一直尊崇,没有最好的技术,只有最合适的技术。
实际上个人组件化经验也不是不少,本文也是根据casa
、limboy
、bang
等的博客中的内容,作了简要的分析,针对文中的不足之处,还望你们指出,共同进步。(文中图片来自互联网,版权归原做者全部)
参考资料