為什么會有這篇文章呢?
和之前的同事"我是你爸爸"討論了關于組件化的事,對我有很大的啟發(fā)。在此特別感謝"我是你爸爸"。
最近寫了關于組件二進制化的文章的文章,有點感觸。
一些朋友來問我關于CocoaPods的問題提到了組件化。
自己一開始準備寫《組件化之路》的博文的,但是后來發(fā)現(xiàn)我的理解是有偏差的。
以上,所以我想寫一篇關于《我所理解的組件化之路》的博文來闡述自己的觀點。
先提出一個新詞,我自己想的。叫做“CocoaPods化”或叫做“l(fā)ibrary化”
什么叫做CocoaPods化?
CocoaPods化也就是我們公司正在做的。隨著業(yè)務的擴展,有了多個App,有了多個Team,我們希望把一些代碼重用。使用CocoaPods把他們做成library是個很好的選擇。也可以說是CocoaPods化之路。
1.和業(yè)務無關。
開始做這件事的時候,我們會容易的想要把那些Util、Category、JSBrige等等這些和業(yè)務無關的源碼搞在一起做成一個一個CocoaPods庫。它們變成了YTXUtilCategory、YTXWebViewJavaScriptBridge、YTXNibBrige、YTXAnimations這些庫。
2.弱業(yè)務
接下來,進一步地我們會把那些比如網(wǎng)絡請求、Server配置、行情圖、行情Socket等等這些弱業(yè)務的源碼搞在一起。她們變成了YTXRequest、YTXServerId、YTXChart、YTXChartSocket、YTXChatUI等等。
為什么說是弱業(yè)務呢,稍微分析下。比如YTXRequest、YTXChartSocket、YTXServerId在公司內(nèi)部各個App,各個Team之間是通用的,在各個業(yè)務組件之間可以重用和組合使用;又帶著鮮明的公司特色,沒法直接開源了就能讓其他開發(fā)者使用。
如果只做到了前2步,我覺得不能稱之為組件化。只能叫做CocoaPods化或Library化。
3.業(yè)務
這一步,到目前來說沒有做。所以沒法舉我自己實際的例子。
比如拿美團App做例子來說。一條業(yè)務線是外賣,一條業(yè)務線是電影。分別由2個Team維護開發(fā)(技術,產(chǎn)品,測試等)。有各自的KPI。這兩條業(yè)務線是自洽的,是分治的。
外賣是一個業(yè)務組件,電影也是一個業(yè)務組件。里面包含了各種內(nèi)容,各種依賴。外賣可以手寫autolayout,電影可以用storyboard。外賣可以用mvc,電影可以mvvm。想怎么搞就怎么搞。他們兩個就像獨立的App一樣。
有不少朋友包括我自己之前,認為做了前2步就是組件化了。只有真正做到了第3步,并且完善了相關架構(gòu),我才認為能稱之為組件化。
那么我們來看看真正的組件化應該包含什么,什么情況適合組件化。業(yè)界內(nèi)部的討論已經(jīng)有很多了,我來列舉下我自己的看法。
畫一個圖:

適合的情況
- 業(yè)務上要分治。
- Team規(guī)模大,30人+。
- 業(yè)務越來越多,越來越大。
如果不符合這些情況,我認為做組件化沒有意義。因為性價比太低。
有一種情況表面上都符合上面列的條件,但實際上不適合組件化:例如我們公司。雖然有好幾個iOS Team,雖然總?cè)藬?shù)上超過了30人,但每一個Team都只有6~10人。每個Team各自維護各自的一個App,各個App業(yè)務上沒有交集,只公用1和2步的CocoaPods庫。就算有交集,做相同的業(yè)務,也不打算公用或重用這部分代碼(內(nèi)部有競爭關系)。
我們公司這種情況就像是拆分成了好幾個無關的小公司,大家都用了github上的一些CocoaPods庫一樣。
還好早期推了第1步和第2步,避免了每個Team之間都去造差不多功能的輪子,而能把精力盡量集中在各自的業(yè)務上,避免了一些資源浪費。
我認為需要包含什么(不分先后順序)
- App生命周期及事件如何下發(fā)給業(yè)務組件。
- 業(yè)務組件之間沒有依賴關系,需要解耦。
- 解決組件化頁面跳轉(zhuǎn)的問題。
- 解決業(yè)務組件之間通信的問題。
- 解決如何劃分抽象業(yè)務組件、基礎功能組件(業(yè)務無關)和弱業(yè)務組件。
- 統(tǒng)一的網(wǎng)絡服務,本地存儲方式等。
- 去Model化。
- 如何披露接口信息,調(diào)用方式,參數(shù)等等。
- 明確組件的生命周期。
- 提供二進制化方案。
- 組件的subspec。
- 版本規(guī)范。
- 持續(xù)集成。
- 代碼準入制度。
- 統(tǒng)一的命名規(guī)范。
- 集成調(diào)試。
- 代碼維護。
所以我們得出的結(jié)論是:不輕易組件化。而是統(tǒng)籌規(guī)劃好以上所有的內(nèi)容??梢圆挥靡徊骄臀蝗孔龊?,但要預先想好每一步的解決方案;能夠承上啟下。
如果你要問我說哪一步比較重要,我覺得都挺重要的。要結(jié)合自己的實際情況,去排一個優(yōu)先級。
App生命周期及事件如何下發(fā)給業(yè)務組件
例如:applicationDidEnterBackground,didRegisterUserNotificationSettings,didReceiveRemoteNotification等等。
通過注冊方式,App向注冊的業(yè)務組件中的協(xié)議發(fā)送消息。
業(yè)務組件之間沒有依賴關系,需要解耦
通過依賴協(xié)議,或依賴下沉等方式解耦。準確拆分業(yè)務組件,弱業(yè)務組件,基礎功能組件。保證單一原則、DRY 原則等。
解決組件化頁面跳轉(zhuǎn)的問題
各種router。比如MGJRouter。
我不建議是淡出使用URL傳參。理由是可以傳參的對象受限制。
我們自己有一套叫GOTO的東西。使用分類。唯一的問題,你需要知道你要跳轉(zhuǎn)頁面的去model化參數(shù)是什么,代表該頁面的枚舉是什么,目前沒法注冊。
解決業(yè)務組件之間通信的問題
組件間需要相互調(diào)用,監(jiān)聽回調(diào)。不是說不能相互依賴么?對,可以通過依賴協(xié)議或中間件(依賴下沉)等方式解決這個問題。比如CTMediator。CTMediator應該是屬于依賴下沉的方式。
解決如何劃分抽象業(yè)務組件、基礎功能組件(業(yè)務無關)和弱業(yè)務組件
這個得要從各自的實際情況出發(fā)。但有幾個原則可以借鑒:
- 重要性
- 重用性
- 單一性
統(tǒng)一的網(wǎng)絡服務,本地存儲方案等
可以通過創(chuàng)建弱業(yè)務Pod庫解決這個問題。
為什么要這么做?
Team之間人員調(diào)動后可以快速入手。
去Model化
業(yè)務組件間通訊盡量去Model化。否則就得把該Model單獨做成Pod庫。
去Model化后,比如使用NSDictionary如何及時傳播具體的參數(shù)信息?(文檔?口口相傳?寫在頭文件?)
如何披露接口信息、調(diào)用方式、參數(shù)和一些規(guī)則等等
文檔?口口相傳?寫在頭文件?使用協(xié)議?
各有利弊和適用場景。
按目前情況,我們選擇寫在頭文件。
明確組件的生命周期。
明確組件的生命周期,就能在App中統(tǒng)一的創(chuàng)建,注冊,集成,協(xié)作,銷毀。
提供二進制化方案
二進制化方案能夠提高編譯速度,提升開發(fā)效率。集中注意力在自己維護的業(yè)務組件上。
二進制化方案。
組件的subspec。
subspec教程。
使用subspec可以降低集成調(diào)試門檻。集中注意力在自己維護的業(yè)務組件上。讓組件間依賴更清晰。
版本規(guī)范
可以參考semver。
也可以參考我們的:
組件的依賴版本盡量寬泛一點,精確到minor就行。在App里精確到patch就可以了。然后大家只要按照規(guī)范發(fā)版本就可以了。參考一下這個規(guī)范。
持續(xù)集成
主要工具可以有:gitlab runner,jenkins,fastlne,fir.im。
持續(xù)集成我們是這樣做的。
CI工具是gitlab runner。每當一定條件下,會觸發(fā)build IPA并且上傳到fir.im。
dev分支用的是dev證書。
master分支用的是adhoc證書。
測試人員可以通過http://fir.im/TestXXApp或http://fir.im/XXApp來分別下載。
.gitlab-ci.yml中的構(gòu)建和上傳看起來是這樣的:
xcodebuild -exportArchive -archivePath 'build/p4.xcarchive' -exportPath 'build' -exportOptionsPlist exportOptionsDebug.plist | xcpretty
fir publish build/*.ipa -c $CI_BUILD_REF -T $FIR_TOKEN_DEBUG
在組件化開發(fā)中,一定條件應該是:
- 業(yè)務組件發(fā)版更新(會自動修改App的Podfile,然后正常push。修改的部分不只是業(yè)務組件的版本號,業(yè)務組件可能需要更高版本的其他組件或第三方組件,它會在Podfile中一并修改這些庫的版本號)
- dev/master分支正常push
- 手動觸發(fā)
代碼準入
Build/Test/Lint,code review,CI。
有了CI,就可以談談代碼準入了。
- Build正常構(gòu)建成功
- 單元測試通過(我們用的Kiwi)
- Lint通過
- deploymate檢查API
- OCLint檢查代碼
- CocoaPods Lint。不僅會Build一遍,還會檢查podspec相關內(nèi)容設置的對不對。如果沒有用--allow-warnings的參數(shù),有waring發(fā)生Lint是會不通過的。(建議把warning當作error,不要使用--allow-warings參數(shù))
- Code Review。
- 檢查發(fā)版規(guī)范。比如:我們更改了一個弱業(yè)務組件,升了一個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)
命名規(guī)范
公司名+組件名+具體名字
集成調(diào)試
各自業(yè)務組件如何調(diào)試?應該就和在主App中一樣,只需要在Example App中依賴相關的其他業(yè)務組件即可。
另一種情況是,當業(yè)務組件版本更新時需要自動修改主App的Podfile中的版本,自上而下的觸發(fā)集成。
代碼維護
誰來維護基礎功能組件和弱業(yè)務組件?如何保證某個Team提交代碼后不會影響其他Team。(包含了:代碼準入,集成調(diào)試,相互協(xié)作,版本規(guī)范)
需要一個Team專門來做這個事情。
補充:寫在主App中的業(yè)務,要把自己當作業(yè)務組件,不能夠依賴其他業(yè)務組件。