零. 前言
“蜂鳥配送商家版”是一款針對商家打造的專業(yè)配送軟件尿孔,有了這款應(yīng)用滞项,您可以使用蜂鳥商家版呼叫所有平臺訂單及電話訂單配送,餐飲睛蛛、鮮花鹦马、蛋糕、生鮮忆肾、商超均可配送荸频。超低運費,清晰合理客冈。海量補貼旭从,充值返現(xiàn)。
以上這段對「蜂鳥商家版」的描述摘自 蜂鳥配送官網(wǎng)场仲,大概可以理解為蜂鳥商家版是一個給廣大商家用來發(fā)單呼叫配送員的 App和悦。許多同學(xué)可能只聽說過「餓了么」外賣應(yīng)用,但是對支撐起外賣配送的后勤業(yè)務(wù)「蜂鳥配送」卻知之甚少渠缕,實際上每天海量的外賣訂單都是由蜂鳥配送系統(tǒng)進(jìn)行處理和配送最終送到消費者手中的鸽素。外賣 O2O 是由外賣平臺、商戶亦鳞、配送系統(tǒng)這三方合作共同完成的馍忽,缺一不可棒坏。O2O 最核心的價值就是人與服務(wù)的連接,而這種連接最終都是通過配送才得以實現(xiàn)的舵匾。
自 2016 年底開始我參與蜂鳥商家版的維護(hù)工作俊抵,除了日常的開發(fā)迭代以外,期間還參與推進(jìn)了項目 Swift 化坐梯、項目組件化 / 模塊化徽诲、非業(yè)務(wù)組件開源化等技術(shù)改造工作,今天這篇文章就給大家分享一下蜂鳥商家版 iOS 的組件化 / 模塊化實踐過程和自己的心得體會吵血。
一. 背景分析
蜂鳥商家版 iOS 端代碼使用 Git 進(jìn)行管理谎替,代碼托管在內(nèi)網(wǎng)的 GitLab 上。項目的依賴管理工具是大家比較熟悉的 CocoaPods蹋辅,除了 RN 模塊為了和 Android 組公用采用 Submodule 進(jìn)行管理外钱贯,其他所有的子模塊都采用 Pods 庫的方式引入。
1. 存在的問題
在「蜂鳥商家版 iOS 組件化 / 模塊化」工作開展之前侦另,項目主要存在如下這些問題:
- 項目臃腫不堪
在組件化 / 模塊化之前秩命,蜂鳥商家版 App 的所有代碼 / 資源文件等都是在同一個主工程里的,只有 RN 倉庫或組內(nèi)公用私有庫等極少部分代碼游離于主工程之外褒傅,所以在開發(fā)時弃锐,每一次都要編譯整個項目的所有代碼,十分低效殿托。這個問題在獨立開發(fā)時還不是十分明顯霹菊,畢竟雖然項目大但是代碼只有一個人在提交,所以項目代碼量增加也不是那么夸張而且對項目發(fā)生的變化比較熟悉支竹。但是當(dāng)多人協(xié)作開發(fā)時旋廷,這個缺陷就暴露了出來,大家在各自開發(fā)不同的業(yè)務(wù)時礼搁,不僅要時刻和他人同步項目變化饶碘、讀懂他人代碼,還要每次編譯完整個項目才能對自己所做的一點修改進(jìn)行調(diào)試馒吴,效率低下熊镣。
- 團(tuán)隊規(guī)模變化
我開始參與蜂鳥商家版 iOS 端的維護(hù)時,之前只有一個前輩在維護(hù)募书,也就是一個人獨立維護(hù)一個 App绪囱。然后過了沒多久,他離職去了另一家公司莹捡,所以又變成了一個人獨立維護(hù)這個 App鬼吵。這時候因為是獨立開發(fā),所以也不存在什么太大的問題篮赢。但隨著團(tuán)隊擴(kuò)大齿椅,后面陸續(xù)來了幾位同事共同負(fù)責(zé)這個項目的維護(hù)工作琉挖,大家都在同一個工程上進(jìn)行業(yè)務(wù)開發(fā),經(jīng)常遇到如代碼沖突涣脚、開發(fā)效率低下示辈、職責(zé)劃分不清、代碼管理混亂等問題遣蚀。
- 業(yè)務(wù)發(fā)展壓力
由于公司處在高速發(fā)展的階段矾麻,業(yè)務(wù)增長很快,最直觀的表現(xiàn)就是市場 & 客服部門不斷接到大量一線使用者的使用反饋或訴求芭梯,最后就變成了產(chǎn)品展示給我們開發(fā)人員的一份接一份的 PRD险耀。緊湊的業(yè)務(wù)開發(fā)需求和各種靈活的功能迫使我們想盡一切能夠使用的辦法來提高開發(fā)效率,提高提測質(zhì)量玖喘。
- 代碼管理混亂
當(dāng)我開始參與這個項目的維護(hù)時甩牺,這個項目就已經(jīng)是一個 Swift 和 OC 混編的項目了,然后還有 RN 和 H5 代碼累奈,可以說是十分復(fù)雜了贬派。雖然這不是我廠唯一一個 Swift 和 OC 的混編項目,但絕對是當(dāng)時 Swift 化最高的一個項目澎媒,約 25% 的代碼為 Swift赠群。眾所周知,Swift 和 OC 的互相調(diào)用遠(yuǎn)不如 Java 和 Kotlin 的互相調(diào)用那么順滑(反正你現(xiàn)在知道了)旱幼,并且處處藏著危機(jī),暗坑無數(shù)突委,所以迫切需要找一個方式柏卤,將 Swift 和 OC 代碼進(jìn)行整理、轉(zhuǎn)換或者分隔匀油。畢竟缘缚,這個文件是 OC 下一個文件就是 Swift 這種頻繁的思維轉(zhuǎn)換在業(yè)務(wù)開發(fā)這種本就十分緊張的場景下,會使人十分疲憊敌蚜,不利于開發(fā)工作的順利進(jìn)行桥滨。
[圖片上傳中...(image-6e2ada-1551146233940-4)]
<figcaption></figcaption>
2. 怎樣去解決
為了解決以上這些問題,我們曾經(jīng)進(jìn)行過如下一些探索:
- 移除無用的第三方庫和資源文件弛车,減少打包時間:效果不明顯齐媒;
- 整理并推動內(nèi)部 Gitflow 工作流,提高協(xié)作效率:有一些效果纷跛,但由于項目過大喻括,日常協(xié)作仍然吃力;
- 研究 Swift 編譯時間優(yōu)化方法贫奠,提高編譯效率:發(fā)現(xiàn)增加編譯時間的都是 Swift 的一些常用語法糖唬血,如果不用的話望蜡,嚴(yán)重降低開發(fā)效率,遂放棄拷恨;
- 在不拆分主工程的情況下脖律,推動項目整個 Swift 化:由于之前維護(hù)項目的前輩離職,導(dǎo)致目前的項目開發(fā)人員都對原代碼不是十分熟悉腕侄,不敢妄加改動小泉,加之業(yè)務(wù)迭代頻繁,開發(fā)和測試資源都十分緊張兜挨,該工作工作推進(jìn)十分緩慢膏孟。
可以發(fā)現(xiàn)上述嘗試的結(jié)果都不是十分理想,在與 iOS 組內(nèi)大佬們進(jìn)行一些溝通拌汇,聽取大佬們的意見后柒桑,決定對原項目進(jìn)行「組件化 / 模塊化拆分」工作,它能帶來如下這些好處:
- 加快編譯速度噪舀,不用再編譯組件 / 模塊外沒有被依賴到的代碼魁淳;
- 便于將每個模塊指定給不同負(fù)責(zé)人進(jìn)行管理;
- 降低合并難度与倡,減小沖突和出錯概率界逛,提高業(yè)務(wù)開發(fā)效率;
- 將 Swift 和 OC 代碼進(jìn)行分離纺座,便于進(jìn)一步 Swift 化工作的推進(jìn)息拜;
- 可為模塊編寫單元測試,提高工作效率净响,同時方便測試人員進(jìn)行有針對性的測試少欺。
二. 目標(biāo)設(shè)定
- 功能組件獨立:保證所有的底層功能組件從主工程抽出,獨立與主工程之外馋贤,便于復(fù)用赞别、業(yè)務(wù)模塊的調(diào)用;
- 業(yè)務(wù)模塊劃分與拆解:將業(yè)務(wù)按對應(yīng)用途進(jìn)行劃分和拆解配乓,想辦法切斷各業(yè)務(wù)之間的強依賴仿滔;
- 所有組件 / 模塊獨立編譯:所有功能組件和業(yè)務(wù)模塊能夠獨立于主工程進(jìn)行編譯,有各自的 Demo 工程犹芹;
- CocoaPods 發(fā)布:在內(nèi)網(wǎng) GitLab 進(jìn)行發(fā)布崎页,并且之后對每個模塊用 GitFlow 工作流進(jìn)行管理和后續(xù)發(fā)布工作。
[圖片上傳中...(image-f99687-1551146233940-3)]
<figcaption></figcaption>
三. 計劃制定
說到組件化 / 模塊化腰埂,那么什么是組件化 / 模塊化呢实昨?組件化和模塊化的區(qū)別又在哪里呢?
組件盐固,就是我們對功能的封裝荒给,一個功能就是一個組件丈挟,數(shù)據(jù)庫、網(wǎng)絡(luò)志电、文件操作曙咽、社會化分享等等這些功能都是組件。我們之所以要搞出組件的概念挑辆,是為了能夠讓我們的上層業(yè)務(wù)模塊能夠隨時依賴和調(diào)用這些基礎(chǔ)功能例朱。組件基本上可以分為基礎(chǔ)功能組件、通用 UI 組件鱼蝉、基礎(chǔ)業(yè)務(wù)組件等這幾類洒嗤。所以為了滿足上述要求,組件必須具有較高的獨立性魁亦、擴(kuò)展性以及復(fù)用性渔隶。
模塊,就是對一系列有內(nèi)聚性的業(yè)務(wù)進(jìn)行整理洁奈,將其與其它業(yè)務(wù)進(jìn)行切割间唉、拆分,從主工程或原所在位置抽離為一個相對獨立的部分利术。僅僅針對業(yè)務(wù)而言呈野,比如說我們可以把訂單業(yè)務(wù)獨立為為一個模塊,可以把個人中心獨立為一個模塊印叁,把用戶登錄獨立為一個模塊等被冒,在 App 中的體現(xiàn)就是一個個獨立的 Git 倉庫。模塊化的一個好處是用到時可以搭積木轮蜕,比如可以多個工程間復(fù)用同一個或幾個業(yè)務(wù)模塊昨悼,比如騰訊的 QQ 和 TIM,除了 UI 界面外 TIM 顯然復(fù)用了大量現(xiàn)有的原 QQ 工程的業(yè)務(wù)模塊代碼肠虽,當(dāng)然,我們這里暫時并沒有這個需求玛追。
經(jīng)過小組會議討論税课,我們的想法是將共用組件獨立出來,然后直接按業(yè)務(wù)對現(xiàn)有主工程進(jìn)行拆分同時兼顧 Swift 與 OC 分離痊剖,大致劃分如下表所示:
1. 組件
組件 | 庫名 | 主要內(nèi)容 |
---|---|---|
基礎(chǔ)(OC) | LPDBOCFoundationGarbage | 基礎(chǔ)的 OC 組件韩玩,各種零散的、混亂的視圖陆馁、組件找颓、控件、常量叮贩、OC 宏定義等击狮,全放在這里佛析,供上層調(diào)用。和他的庫名一樣彪蓬,其本質(zhì)就大概就是個垃圾桶寸莫。 |
基礎(chǔ)(Swift) | LPDBPublicModule | 基礎(chǔ)的 Swift 組件,包含一些公用的 Swift 擴(kuò)展档冬,和模塊間解耦的協(xié)議膘茎。 |
網(wǎng)絡(luò)(OC) | LPDBNetwork | 網(wǎng)絡(luò)組件,對 AFNetworking 的淺層封裝酷誓,同時包含了和網(wǎng)絡(luò)相關(guān)的業(yè)務(wù)功能披坏。 |
... | ... | ... |
2. 模塊
模塊 | 庫名 | 主要內(nèi)容 |
---|---|---|
歷史(OC) | LPDBHistoryModule | 歷史訂單模塊,包含和歷史訂單相關(guān)的資源文件盐数、UI棒拂、業(yè)務(wù)邏輯代碼等。 |
登錄(OC) | LPDBLoginModule | 用戶登錄模塊娘扩,包含和登錄着茸、注冊頁面相關(guān)的資源文件、UI琐旁、業(yè)務(wù)邏輯代碼等缭保。 |
用戶中心(OC) | LPDBUserCenterModule | 用戶中心模塊身坐,包含和用戶個人中心以及狀態(tài)相關(guān)的資源文件、UI、業(yè)務(wù)邏輯代碼等扩氢。 |
... | ... | ... |
3. 關(guān)系
按照上面的思路,理想化的模塊 / 組件依賴關(guān)系圖大概是這個樣子的:
[圖片上傳中...(image-87a331-1551146233940-2)]
<figcaption></figcaption>
因為蜂鳥商家版的團(tuán)隊開發(fā)人員之前均沒有過任何項目的拆分經(jīng)驗捡需,大家也都是摸著石頭過河愈魏,走一步看一步。所以雖然以上的拆分思路總體是對的掰伸,先拆組件后拆業(yè)務(wù)皱炉,但由于各種各樣的原因,一些問題就在接下來的工作實施過程中暴露了出來狮鸭。
四. 工作實施
我們小組主要還是以業(yè)務(wù)開發(fā)為主合搅,所以組件化 / 模塊化工作都是大家抽空閑時間來完成,并沒有進(jìn)行硬性的排期和設(shè)置 Deadline歧蕉。按照之前制定的計劃灾部,我們進(jìn)行了以下這些工作:
1. 功能組件獨立
1.1 LPDBOCFoundationGarbage
LPDBOCFoundationGarbage 是我們項目最先抽出的部分,這個庫將和 LPDBPublicModule 一起惯退,作為整個工程的最底層赌髓,再往下就是。這個庫的定位和它的名字一樣,就是一個垃圾桶锁蠕,啥都往里放夷野。其中大致包含以下一些東西:
- 自定義的 View 和控件,例如:小紅點控件匿沛、刷新控件扫责、加載控件、Tips 視圖等逃呼;
- 自定義的 Controller鳖孤,例如:基礎(chǔ)控制器 BaseViewController、WebView 基礎(chǔ)控制器 BaseWebViewController抡笼、自定義的彈框 AlertController等苏揣;
- 和業(yè)務(wù)相關(guān)的對基本類型或系統(tǒng)控件的擴(kuò)展:對 NSObject、UIButton推姻、UIImageView平匈、UILabel 等添加的擴(kuò)展代碼 category;
- 甚至版本控制模塊 LPDBVersionManager 也放在了這里藏古。
因為我們在進(jìn)行拆分任務(wù)的同時增炭,還在同時維持著項目的開發(fā)工作,所以我們暫時沒有精力做細(xì)致的拆分工作拧晕,只能先把這些零散的部分先放在一起進(jìn)行管理隙姿。
1.2 LPDBPublicModule
LPDBPublicModule 是基礎(chǔ)的 Swift 組件,這個庫主要包含:
- 一些公用的 Swift 擴(kuò)展厂捞,例如:對 CGFloat输玷、Date、NSString 等系統(tǒng)類型的 extension靡馁;
- 用于模塊間解耦的協(xié)議欲鹏。
因為工程內(nèi)的 Swift 代碼大多是我們新寫的,所以相對舊的 OC 代碼而言臭墨,整理地更好一些赔嚎,所以這個倉庫干凈很多
1.3 LPDBNetwork
LPDBNetwork 網(wǎng)絡(luò)組件是我們項目完成 OC 和 Swift 基礎(chǔ)部分后最先抽出的部分,剛開始我們認(rèn)為這部分僅僅是單純的業(yè)務(wù)網(wǎng)絡(luò)請求操作和對 AFNetworking 的淺層封裝胧弛,不包含界面 UI 邏輯等尤误。不過當(dāng)我們拆解完成后,發(fā)現(xiàn)其中還包含了一堆奇怪的東西:
- 對 AFNetworking 的封裝和網(wǎng)絡(luò)操作的一些定義叶圃,例如:LPDBHttpManager袄膏、LPDBRequestObject 和 LPDBModel 等践图;
- UI 操作掺冠,例如:等待視圖 LPDBLoadingView 和 網(wǎng)絡(luò)請求失敗的提示等。
這一部分的話,因為都是比較古老的代碼德崭,所以當(dāng)初的開發(fā)人員都已經(jīng)不再繼續(xù)維護(hù)了斥黑,所以在只能是我們自己進(jìn)行拆分的情況下,為了防止大的變更導(dǎo)致發(fā)生問題眉厨,所以沒有對這一塊進(jìn)行更細(xì)致的拆解工作锌奴。畢竟再爛代碼也比不能工作的代碼要好。
1.4 LPDBUIKit
Swift 的 UI 庫憾股,我們將工程中的一些 Swift 視圖和控件收集到了這個項目中鹿蜀,主要包含以下這些內(nèi)容:
- 視圖,例如:LPDBEmptyDataView服球、SlideScrollView 等茴恰;
- 控件,例如:SlideTabKit 等斩熊。
因為 Swift 代碼總量還不是很大往枣,所以這個庫的東西目前也不是很多,以后會逐漸豐富起來粉渠。
2. 業(yè)務(wù)模塊拆分
完成了上面的組件庫的獨立工作后分冈,業(yè)務(wù)模塊的拆解就相對輕松一些了,目前我們主要完成了三個業(yè)務(wù)模塊的拆分工作霸株。
2.1 LPDBHistoryModule
LPDBHistoryModule 歷史訂單模塊雕沉,和歷史訂單頁面相關(guān)的信息都在該模塊中,主要包含以下內(nèi)容:
- UI淳衙,例如:歷史訂單界面蘑秽、歷史訂單列表 Cell、加載視圖等箫攀;
- 數(shù)據(jù)模型肠牲,例如:歷史訂單模型;
- 歷史訂單列表相關(guān)的網(wǎng)絡(luò)請求靴跛。
因為該模塊相對來說比較獨立缀雳,所以拆分過程也比較順利,主要依賴了 LPDBPublicModule梢睛、LPDBNetwork肥印、LPDBOCFoundationGarbage 組件。
2.2 LPDBLoginModule
LPDBLoginModule 用戶登錄模塊是一個與用戶登錄绝葡、注冊以及用戶登錄信息有關(guān)的模塊深碱,主要包含了以下信息:
- UI,例如:用戶登錄界面藏畅、用戶注冊界面等敷硅;
- 數(shù)據(jù)模型,例如:用戶信息模型、用戶信息地址模型等绞蹦;
- 登錄與注冊相關(guān)的網(wǎng)絡(luò)請求力奋。
該模塊相比較歷史訂單模塊復(fù)雜了一些,不過仍然比較順利幽七,主要依賴了 LPDBPublicModule景殷、LPDBOCFoundationGarbage、LPDBNetwork 組件澡屡。
2.3 LPDBUserCenterModule
LPDBUserCenterModule 用戶中心模塊是一個與用戶個人中心以及用戶信息修改有關(guān)的模塊猿挚,主要包含了以下信息:
- UI,例如:用戶中心界面驶鹉、用戶電話修改界面亭饵、用戶密碼修改界面等;
- 數(shù)據(jù)模型梁厉,例如:用戶詳細(xì)信息模型辜羊、用戶信息地址模型等;
- 用戶中心相關(guān)的網(wǎng)絡(luò)請求词顾,例如:修改電話號碼八秃、請求驗證碼等。
該模塊主要依賴了 LPDBOCFoundationGarbage 組件和 LPDBLoginModule 模塊肉盹。
2.4 其它
剩下的其他一些模塊仍然處于計劃中的狀態(tài)昔驱,暫未進(jìn)行拆分。到這一步的話上忍,庫間依賴關(guān)系大致如下圖所示:
[圖片上傳中...(image-d579c7-1551146233939-1)]
<figcaption></figcaption>
可以看到其中存在一些不太合理的依賴關(guān)系骤肛,如 LPDBUserCenterModule 依賴 LPDBLoginModule 模塊,也就是所謂的業(yè)務(wù)模塊橫向依賴問題窍蓝,接下來腋颠,我們就要處理這一問題。
3. 解除耦合
由于之前開發(fā)過程中從未有過任何模塊化的考量吓笙,所以蜂鳥商家版的代碼非常雜糅淑玫,項目依賴關(guān)系十分復(fù)雜,主要可以分為以下三類耦合:
- 界面耦合:App 執(zhí)行過程中面睛,硬編碼的界面間的跳轉(zhuǎn)行為絮蒿;
- 工程耦合:某些模塊在運行時需要依賴主工程的代碼才能運行或?qū)崿F(xiàn)完整的功能;
- 依賴耦合:兩個業(yè)務(wù)模塊之間的有依賴叁鉴。
3.1 模塊間組件共用
在拆分業(yè)務(wù)模塊的過程中土涝,經(jīng)常發(fā)生兩個業(yè)務(wù)模塊同時引用某一塊業(yè)務(wù)代碼的問題,這時我們就需要對這一塊代碼進(jìn)行理解幌墓,首先區(qū)分它到底應(yīng)不應(yīng)該劃分到業(yè)務(wù)層來但壮?
- 如果是的話狗准,應(yīng)該劃歸到哪一個模塊中去更合理一些;
- 如果不是的話茵肃,應(yīng)該將這一部分代碼下沉到哪一個組件庫中去比較合適,或者獨立為一個組件袭祟。
在 LPDBUserCenterModule 的抽離過程中就遇到了這個問題验残,LPDBUserCenterModule 和 LPDBLoginModule 共同依賴了幾個和用戶信息有關(guān)的數(shù)據(jù)模型,導(dǎo)致需要發(fā)生模塊間橫向依賴巾乳,所以我們將共用的數(shù)據(jù)模型抽出您没,然后下沉到了 LPDBOCFoundationGarbage 中。
3.2 模塊間耦合
另一個經(jīng)常遇到的問題就是跨模塊調(diào)用代碼的問題了胆绊,不僅是模塊與模塊間代碼的互相調(diào)用氨鹏、模塊間頁面的跳轉(zhuǎn),還有模塊反向調(diào)用主工程代碼等問題压状,這個問題的解決我們分了三步:
- 反射調(diào)用
因為工程的復(fù)雜性和以前代碼的不規(guī)范仆抵,導(dǎo)致我們在處理切割業(yè)務(wù)模塊時比較痛苦,所以我們在剛開始抽出模塊時采用了一種快速但不太安全的方式進(jìn)行解耦种冬,比如在 LPDBUserCenterModule 模塊中需要調(diào)用主工程的 getMiddlePageVC 方法時镣丑,我們用了如下臨時解決方案:
if ([[UIApplication sharedApplication].delegate respondsToSelector:@selector(getMiddlePageVC)]) {
UIViewController *info = [[UIApplication sharedApplication].delegate performSelector:@selector(getMiddlePageVC)];
...
}
復(fù)制代碼
然后在主工程的 中實現(xiàn)這個接口:
// .h
@interface AppDelegate : UIResponder <UIApplicationDelegate>
...
// LPDBUserCenterModule
- (UIViewController *)getMiddlePageVC;
...
@end
// .m
@implementation AppDelegate
...
- (UIViewController *)getMiddlePageVC {
...
return xxx;
}
...
@end
復(fù)制代碼
這一方案的優(yōu)點就是靈活,利用 NSClassFromString娱两、performSelector 等方式莺匠,能夠快速解決各種耦合問題,瞬間切割出模塊十兢。但缺點也顯而易見趣竣,字符串硬編碼,維護(hù)成本大旱物,去掉了編譯器檢查遥缕,容易翻車。
- 協(xié)議調(diào)用
所以自然而然地宵呛,當(dāng)我們的某個業(yè)務(wù)模塊的拆分工作基本定型時通砍,我們就開始將第一步中的反射調(diào)用方式替換為協(xié)議的方式進(jìn)行調(diào)用,比如當(dāng) LPDBLoginModule 模塊需要調(diào)用主工程的 getCoordinate 方法時烤蜕,示例如下:
id delegate = [[UIApplication sharedApplication] delegate];
if (![delegate conformsToProtocol:@protocol(AppDelegateProtocol)]) {
return;
}
CLLocationCoordinate2D coordinate = [delegate coordinate];
復(fù)制代碼
然后在主工程中實現(xiàn)該方法:
// .h
#import "AppDelegate.h"
@import LPDBLoginModule;
@interface AppDelegate (Protocol) <AppDelegateProtocol>
@end
// .m
@implementation AppDelegate (Protocol)
- (CLLocationCoordinate2D)getCoordinate {
return self.coordinate;
}
@end
復(fù)制代碼
但是封孙,樣的改變并不能徹底解決所編寫的模塊間互相調(diào)用的代碼缺乏編譯器檢查的問題,而僅僅是對調(diào)用方做了判斷加上了容錯讽营,并不能在編譯期就讓開發(fā)人員察覺到問題虎忌,一定要進(jìn)行測試才可以,所以這種方式也不是十分理想橱鹏。
- Lotusoot 解耦工具
那么為了徹底解決問題膜蠢,我們開發(fā)和引入了組件通信和工具 Lotusoot堪藐,調(diào)用方式有下列幾種可供參考:
- 服務(wù)調(diào)用
let lotus = s(AccountLotus.self)
let accountModule: AccountLotus = LotusootCoordinator.lotusoot(lotus: lotus) as! AccountLotus
accountModule.login(username: "admin", password: "wow") { (error) in
print(error ?? "")
}
復(fù)制代碼
- 短鏈注冊
let error: NSError? = LotusootRouter.register(route: "newproj://account/login") { (lotusootURL) in
accountModule.showLoginVC(username: "admin", password: "wow")
}
復(fù)制代碼
- 短鏈調(diào)用
let param: Dictionary = ["username" : "admin",
"password" : "wow"]
// 無回調(diào)
LotusootRouter.open(route: "newproj://account/login", params: param)
// 有回調(diào)
LotusootRouter.open(route: "newproj://account/login", params: param).completion { (error) in
print(error ?? "open success")
}
// ??不推薦的用法,用 ?pram0=xxx 這樣的形式導(dǎo)致字符串散落在各處挑围,不易管理礁竞。
// 但為了保證 Hybrid 項目中 H5 頁面的正常跳轉(zhuǎn),提供了此種調(diào)用
LotusootRouter.open(url: "newproj://account/login?username=zhoulingyu").completion { (error) in
print(error ?? "open success")
}
復(fù)制代碼
具體可以參見 iOS 靈活的 模塊化/組件化 工具與規(guī)范 Lotusoot 解說 一文杉辙,在此不多做贅述模捂。類似的工具還有 BeeHive 和 LPDMvvmRouterKit 等,大家可以自行進(jìn)一步探索蜘矢。
最終結(jié)構(gòu)就變成了如圖所示的樣子:
[圖片上傳中...(image-e02232-1551146233939-0)]
<figcaption></figcaption>
五. 問題整理
1. 不合理的分層結(jié)構(gòu)和庫間依賴
由于參與拆分工作的人員比較缺乏組件化經(jīng)驗狂男,所以導(dǎo)致某些庫的拆分不是十分合理,某些應(yīng)該沉入底層的公用 Model 和常量等沒有在開始時就放到一個合理的位置品腹。業(yè)務(wù)模塊之間也存在一些不合理的橫向依賴岖食,沒有進(jìn)行一個合理的業(yè)務(wù)邊界劃分。這些原因?qū)е挛覀冊谶M(jìn)行拆分工作時經(jīng)常需要回過頭來對已經(jīng)拆出來的模塊和組件重新進(jìn)行整理和處理舞吭,重復(fù)勞動量很大泡垃。
2. 拆分粒度不適中
某些庫比如 LPDBOCFoundationGarbage 比較龐大,而像 LPDBUIKit 這樣的庫中內(nèi)容卻非常少羡鸥,這一點的處理上存在問題兔毙。如果一個拆分完成的庫仍然比較臃腫的化,說明仍然存在細(xì)化拆分的必余地兄春。
3. 工作進(jìn)度難以控制
由于沒有能提前制定好詳細(xì)的進(jìn)度計劃表澎剥,加上業(yè)務(wù)工作的擠壓,導(dǎo)致我們花在組件化 / 模塊化工作上的時間比較零散赶舆。本意是希望大家能夠靈活安排工作哑姚,合理處置業(yè)務(wù)開發(fā)與技術(shù)改造工作之間的關(guān)系,但效果不是很理想芜茵,表現(xiàn)就是組件化 / 模塊化工作的進(jìn)行沒有連續(xù)性叙量,大家的積極性和工作效率也都不高。
六. 經(jīng)驗總結(jié)
1. 工作開始前要進(jìn)行技術(shù)調(diào)研
查看和學(xué)習(xí)一些同類成功的案例資料或者向業(yè)內(nèi)大佬們請教能夠?qū)τ媱澋闹贫◣肀憷糯軌蚴刮覀儽苊夂芏噱e誤的設(shè)計绞佩,少走一些彎路,降低返工率猪钮。
2. 制定詳細(xì)整體規(guī)劃
在準(zhǔn)備作戰(zhàn)時品山,我常常發(fā)現(xiàn)定好的計劃沒有用處,但計劃的過程仍必不可少烤低≈饨唬—— 德懷特·艾森豪威爾
制定詳細(xì)的整體規(guī)劃能夠在設(shè)計階段就將一些不合理的地方暴露出來,從而拿出解決方案使問題提前得到解決扑馁,或者把不合理的內(nèi)容刪減替換掉涯呻,例如分層不合理凉驻、庫間依賴這樣的問題,就會減少很多复罐。拿出細(xì)致的任務(wù)拆分計劃和工作量預(yù)估涝登,也能更合理地將任務(wù)安排到開發(fā)人員手中,在提升工作效率的同時也能盡量避免和業(yè)務(wù)開發(fā)產(chǎn)生沖突效诅。
3. 注意對代碼質(zhì)量的控制
好的代碼和編碼習(xí)慣能夠大幅提升項目的可維護(hù)性胀滚,為之后的工作帶來便利。我們之前舊的 OC 代碼比較混亂填帽,基本處于無法維護(hù)的狀態(tài),拆分起來十分痛苦咙好;而新寫的 Swift 代碼明顯質(zhì)量要高很多(這真的不是我們自夸...)篡腌,拆分起來就順利多了。
4. 重視信息的文檔化
每一個拆分出的模塊及時添加文檔勾效,嫌麻煩的話至少要建立一份通用的 README 模板嘹悼,每一個模塊或組件的建立者把模塊內(nèi)容、拆分目的层宫、設(shè)計思路等基本信息記錄一下杨伙,有什么坑或者注意點也可以文檔化,使以后的長期項目維護(hù)成為可能萌腿。
七. 開源成果
我們在組件化 / 模塊化工作期間限匣,產(chǎn)出的一些庫和工具放在了 GitHub 上進(jìn)行開源,給大家一些借鑒的同時毁菱,也希望能夠收到大家的意見和建議米死,提高我們項目本身的質(zhì)量:
庫名 | 簡介 | 倉庫地址 |
---|---|---|
EFPodsAnalyzer | 可視化 Pods 庫依賴分析工具 | github.com/EyreFree/EF… |
EFAutoScrollLabel | 一個帶跑馬燈效果的 UILabel | github.com/EyreFree/EF… |
Bamboots | 一個面向協(xié)議的 Swift 網(wǎng)絡(luò)庫 | github.com/mmoaay/Bamb… |
Lotusoot | 靈活的 Swift 組件解耦和通信工具 | github.com/Vegetarians… |
bigkeeper | 一個 iOS & Android 模塊化項目效率提升工具 | github.com/BigKeeper/b… |
SideNavigation | 一個支持側(cè)滑且可自定義的側(cè)邊欄 | github.com/CNKCQ/SideN… |
ViewPagers | 一個支持手勢的 Segmented Control | github.com/CNKCQ/ViewP… |
八. 后記
本文基本描述了蜂鳥商家版 App 到目前為止的組件化 / 模塊化實踐情況,希望本文能夠給您的移動項目演進(jìn)提供一些借鑒贮庞。在此過程中我們產(chǎn)出的一些文章峦筒、開源庫和工具,也希望能給大家?guī)硪欢ǖ膸椭蛘邌l(fā)窗慎。歡迎大家提出各種反饋和建議或物喷,幫助我們繼續(xù)改進(jìn)和提高。
2017 年底遮斥,也就是差不多我參與蜂鳥商家版的維護(hù)工作滿一年的樣子峦失,由于業(yè)務(wù)調(diào)整的原因這個 App 已經(jīng)移交給別的團(tuán)隊進(jìn)行維護(hù)了,導(dǎo)致項目的 Swift 化和組件化 / 模塊化工作并沒有全部完成术吗,這一點有些遺憾宠进。不過還是希望蜂鳥商家版能夠越來越好,繼續(xù)為廣大商家朋友們服務(wù)藐翎。
好消息是材蹬,接下來我主要參與蜂鳥團(tuán)隊版 App 的架構(gòu)工作实幕,這一次我們根據(jù)之前暴露出的問題制定了詳細(xì)的工作計劃,有了蜂鳥商家版的踩坑經(jīng)驗后堤器,我相信這一次我們一定能順利完成目標(biāo)昆庇。2018,加油闸溃,一起拼整吆!
本文編寫過程中參考了以下文章,在此對原作者們表示感謝:
- 即時配送網(wǎng)之于外賣O2O辉川,配送的更高境界是社群經(jīng)營
- 談?wù)勎业睦斫?組件化/模塊化
- 蘑菇街 App 的組件化之路
- 豆瓣App的模塊化實踐
- 手機(jī)天貓解耦之路
- 京東iOS客戶端組件管理實踐
九. 后記的后記
「模塊化日潮眚」系列短文,把自己模塊化過程中的踩坑歷程分享出來乓旗,給有(或者還沒有)遇到類似問題的同學(xué)一個參考和幫助:
- 模塊化日常:神奇的 pod repo push 失敗
- 模塊化日常:CocoaPods 1.4.0 真好用(并不)
- 模塊化日常:CocoaPods 庫資源引用問題
- 模塊化日常:重名類
- 模塊化日常:耗時的發(fā)布
- 模塊化日常:開源庫與私有庫重名
- 模塊化日常:庫間互相依賴
未完待續(xù)...2333
作者:EyreFree
鏈接:https://juejin.im/post/5a620cf5f265da3e36415764
來源:掘金
著作權(quán)歸作者所有府蛇。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處屿愚。