我所理解的组件化之路
为什么会有这篇文章呢?
和之前的同事"我是你爸爸"讨论了关于组件化的事,对我有很大的启发。在此特别感谢"我是你爸爸"。
最近写了关于组件二进制化的文章的文章,有点感触。
一些朋友来问我关于CocoaPods的问题提到了组件化。
自己一开始准备写《组件化之路》的博文的,但是后来发现我的理解是有偏差的。
以上,所以我想写一篇关于《我所理解的组件化之路》的博文来阐述自己的观点。
先提出一个新词,我自己想的。叫做“CocoaPods化”或叫做“library化”
什么叫做CocoaPods化?
CocoaPods化也就是我们公司正在做的。随着业务的扩展,有了多个App,有了多个Team,我们希望把一些代码重用。使用CocoaPods把他们做成library是个很好的选择。也可以说是CocoaPods化之路。
1.和业务无关。
开始做这件事的时候,我们会容易的想要把那些Util、Category、JSBrige等等这些和业务无关的源码搞在一起做成一个一个CocoaPods库。它们变成了YTXUtilCategory、YTXWebViewJavaScriptBridge、YTXNibBrige、YTXAnimations这些库。
2.弱业务
接下来,进一步地我们会把那些比如网络请求、Server配置、行情图、行情Socket等等这些弱业务的源码搞在一起。她们变成了YTXRequest、YTXServerId、YTXChart、YTXChartSocket、YTXChatUI等等。
为什么说是弱业务呢,稍微分析下。比如YTXRequest、YTXChartSocket、YTXServerId在公司内部各个App,各个Team之间是通用的,在各个业务组件之间可以重用和组合使用;又带着鲜明的公司特色,没法直接开源了就能让其他开发者使用。
如果只做到了前2步,我觉得不能称之为组件化。只能叫做CocoaPods化或Library化。
3.业务
这一步,到目前来说没有做。所以没法举我自己实际的例子。
比如拿美团App做例子来说。一条业务线是外卖,一条业务线是电影。分别由2个Team维护开发(技术,产品,测试等)。有各自的KPI。这两条业务线是自洽的,是分治的。
外卖是一个业务组件,电影也是一个业务组件。里面包含了各种内容,各种依赖。外卖可以手写autolayout,电影可以用storyboard。外卖可以用mvc,电影可以mvvm。想怎么搞就怎么搞。他们两个就像独立的App一样。
有不少朋友包括我自己之前,认为做了前2步就是组件化了。只有真正做到了第3步,并且完善了相关架构,我才认为能称之为组件化。
那么我们来看看真正的组件化应该包含什么,什么情况适合组件化。业界内部的讨论已经有很多了,我来列举下我自己的看法。
画一个图:
default
适合的情况
- 业务上要分治。
- Team规模大,30人+。
- 业务越来越多,越来越大。
如果不符合这些情况,我认为做组件化没有意义。因为性价比太低。
有一种情况表面上都符合上面列的条件,但实际上不适合组件化:例如我们公司。虽然有好几个iOS Team,虽然总人数上超过了30人,但每一个Team都只有6~10人。每个Team各自维护各自的一个App,各个App业务上没有交集,只公用1和2步的CocoaPods库。就算有交集,做相同的业务,也不打算公用或重用这部分代码(内部有竞争关系)。
我们公司这种情况就像是拆分成了好几个无关的小公司,大家都用了github上的一些CocoaPods库一样。
还好早期推了第1步和第2步,避免了每个Team之间都去造差不多功能的轮子,而能把精力尽量集中在各自的业务上,避免了一些资源浪费。
我认为需要包含什么(不分先后顺序)
- App生命周期及事件如何下发给业务组件。
- 业务组件之间没有依赖关系,需要解耦。
- 解决组件化页面跳转的问题。
- 解决业务组件之间通信的问题。
- 解决如何划分抽象业务组件、基础功能组件(业务无关)和弱业务组件。
- 统一的网络服务,本地存储方式等。
- 去Model化。
- 如何披露接口信息,调用方式,参数等等。
- 明确组件的生命周期。
- 提供二进制化方案。
- 组件的subspec。
- 版本规范。
- 持续集成。
- 代码准入制度。
- 统一的命名规范。
- 集成调试。
- 代码维护。
所以我们得出的结论是:不轻易组件化。而是统筹规划好以上所有的内容。可以不用一步就位全部做好,但要预先想好每一步的解决方案;能够承上启下。
如果你要问我说哪一步比较重要,我觉得都挺重要的。要结合自己的实际情况,去排一个优先级。
App生命周期及事件如何下发给业务组件
例如:applicationDidEnterBackground,didRegisterUserNotificationSettings,didReceiveRemoteNotification等等。
通过注册方式,App向注册的业务组件中的协议发送消息。
业务组件之间没有依赖关系,需要解耦
通过依赖协议,或依赖下沉等方式解耦。准确拆分业务组件,弱业务组件,基础功能组件。保证单一原则、DRY 原则等。
解决组件化页面跳转的问题
各种router。比如MGJRouter。
我不建议是淡出使用URL传参。理由是可以传参的对象受限制。
我们自己有一套叫GOTO的东西。使用分类。唯一的问题,你需要知道你要跳转页面的去model化参数是什么,代表该页面的枚举是什么,目前没法注册。
解决业务组件之间通信的问题
组件间需要相互调用,监听回调。不是说不能相互依赖么?对,可以通过依赖协议或中间件(依赖下沉)等方式解决这个问题。比如CTMediator。CTMediator应该是属于依赖下沉的方式。
解决如何划分抽象业务组件、基础功能组件(业务无关)和弱业务组件
这个得要从各自的实际情况出发。但有几个原则可以借鉴:
- 重要性
- 重用性
- 单一性
统一的网络服务,本地存储方案等
可以通过创建弱业务Pod库解决这个问题。
为什么要这么做?
Team之间人员调动后可以快速入手。
去Model化
业务组件间通讯尽量去Model化。否则就得把该Model单独做成Pod库。
去Model化后,比如使用NSDictionary如何及时传播具体的参数信息?(文档?口口相传?写在头文件?)
如何披露接口信息、调用方式、参数和一些规则等等
文档?口口相传?写在头文件?使用协议?
各有利弊和适用场景。
按目前情况,我们选择写在头文件。
明确组件的生命周期。
明确组件的生命周期,就能在App中统一的创建,注册,集成,协作,销毁。
提供二进制化方案
二进制化方案能够提高编译速度,提升开发效率。集中注意力在自己维护的业务组件上。
二进制化方案。
组件的subspec。
subspec教程。
使用subspec可以降低集成调试门槛。集中注意力在自己维护的业务组件上。让组件间依赖更清晰。
版本规范
可以参考semver。
也可以参考我们的:
组件的依赖版本尽量宽泛一点,精确到minor就行。在App里精确到patch就可以了。然后大家只要按照规范发版本就可以了。参考一下这个规范。
持续集成
主要工具可以有:gitlab runner,jenkins,fastlne,fir.im。
持续集成我们是这样做的。
CI工具是gitlab runner。每当一定条件下,会触发build IPA并且上传到fir.im。
dev分支用的是dev证书。
master分支用的是adhoc证书。
测试人员可以通过http://fir.im/TestXXApp或http://fir.im/XXApp来分别下载。
.gitlab-ci.yml中的构建和上传看起来是这样的:
xcodebuild -exportArchive -archivePath 'build/p4.xcarchive' -exportPath 'build' -exportOptionsPlist exportOptionsDebug.plist | xcpretty
fir publish build/*.ipa -c $CI_BUILD_REF -T $FIR_TOKEN_DEBUG
在组件化开发中,一定条件应该是:
- 业务组件发版更新(会自动修改App的Podfile,然后正常push。修改的部分不只是业务组件的版本号,业务组件可能需要更高版本的其他组件或第三方组件,它会在Podfile中一并修改这些库的版本号)
- dev/master分支正常push
- 手动触发
代码准入
Build/Test/Lint,code review,CI。
有了CI,就可以谈谈代码准入了。
- Build正常构建成功
- 单元测试通过(我们用的Kiwi)
- Lint通过
- deploymate检查API
- OCLint检查代码
- CocoaPods Lint。不仅会Build一遍,还会检查podspec相关内容设置的对不对。如果没有用--allow-warnings的参数,有waring发生Lint是会不通过的。(建议把warning当作error,不要使用--allow-warings参数)
- Code Review。
- 检查发版规范。比如:我们更改了一个弱业务组件,升了一个patch版本号,但其实不只是修了bug,而且还增加了向前兼容的新功能,这个时候应该升的是minor版本号。
- 检查代码风格。
- 检查潜在的bug。
- 检查其他只有人能看得出的问题。
.gitlab-ci.yml中的OCLint和dploymate看起来是这样的:
Deploymate --cli -t jryMobile p4.xcworkspace -V 8.0 -x
xcodebuild -workspace p4.xcworkspace -scheme p4 -configuration Adhoc -archivePath 'build/p4' archive | tee xcodebuild.log | xcpretty
oclint-xcodebuild xcodebuild.log
oclint-json-compilation-database -e Pods -e Chart -e Chart/core/jsoncpp -e RKNotificationHub.m -e TTMessage.mm -e SSNetworkInfo.m -e Tween.mm -e 略...... && echo 'OCLint Passed' || (cat report.json && exit 1)
命名规范
公司名+组件名+具体名字
集成调试
各自业务组件如何调试?应该就和在主App中一样,只需要在Example App中依赖相关的其他业务组件即可。
另一种情况是,当业务组件版本更新时需要自动修改主App的Podfile中的版本,自上而下的触发集成。
代码维护
谁来维护基础功能组件和弱业务组件?如何保证某个Team提交代码后不会影响其他Team。(包含了:代码准入,集成调试,相互协作,版本规范)
需要一个Team专门来做这个事情。
补充:写在主App中的业务,要把自己当作业务组件,不能够依赖其他业务组件。