蜂鳥商家版 iOS 組件化 / 模塊化實(shí)踐總結(jié)

via --- EyreFree

零. 前言

“蜂鳥配送商家版”是一款針對商家打造的專業(yè)配送軟件,有了這款應(yīng)用,您可以使用蜂鳥商家版呼叫所有平臺訂單及電話訂單配送,餐飲、鮮花、蛋糕、生鮮、商超均可配送。超低運(yùn)費(fèi),清晰合理。海量補(bǔ)貼,充值返現(xiàn)。

以上這段對「蜂鳥商家版」的描述摘自 蜂鳥配送官網(wǎng),大概可以理解為蜂鳥商家版是一個(gè)給廣大商家用來發(fā)單呼叫配送員的 App。許多同學(xué)可能只聽說過「餓了么」外賣應(yīng)用,但是對支撐起外賣配送的后勤業(yè)務(wù)「蜂鳥配送」卻知之甚少,實(shí)際上每天海量的外賣訂單都是由蜂鳥配送系統(tǒng)進(jìn)行處理和配送最終送到消費(fèi)者手中的。外賣 O2O 是由外賣平臺、商戶、配送系統(tǒng)這三方合作共同完成的,缺一不可。O2O 最核心的價(jià)值就是人與服務(wù)的連接,而這種連接最終都是通過配送才得以實(shí)現(xiàn)的。

自 2016 年底開始我參與蜂鳥商家版的維護(hù)工作,除了日常的開發(fā)迭代以外,期間還參與推進(jìn)了項(xiàng)目 Swift 化、項(xiàng)目組件化 / 模塊化、非業(yè)務(wù)組件開源化等技術(shù)改造工作,今天這篇文章就給大家分享一下蜂鳥商家版 iOS 的組件化 / 模塊化實(shí)踐過程和自己的心得體會(huì)。

一. 背景分析

蜂鳥商家版 iOS 端代碼使用 Git 進(jìn)行管理,代碼托管在內(nèi)網(wǎng)的 GitLab 上。項(xiàng)目的依賴管理工具是大家比較熟悉的 CocoaPods,除了 RN 模塊為了和 Android 組公用采用 Submodule 進(jìn)行管理外,其他所有的子模塊都采用 Pods 庫的方式引入。

1. 存在的問題

在「蜂鳥商家版 iOS 組件化 / 模塊化」工作開展之前,項(xiàng)目主要存在如下這些問題:

  • 項(xiàng)目臃腫不堪

在組件化 / 模塊化之前,蜂鳥商家版 App 的所有代碼 / 資源文件等都是在同一個(gè)主工程里的,只有 RN 倉庫或組內(nèi)公用私有庫等極少部分代碼游離于主工程之外,所以在開發(fā)時(shí),每一次都要編譯整個(gè)項(xiàng)目的所有代碼,十分低效。這個(gè)問題在獨(dú)立開發(fā)時(shí)還不是十分明顯,畢竟雖然項(xiàng)目大但是代碼只有一個(gè)人在提交,所以項(xiàng)目代碼量增加也不是那么夸張而且對項(xiàng)目發(fā)生的變化比較熟悉。但是當(dāng)多人協(xié)作開發(fā)時(shí),這個(gè)缺陷就暴露了出來,大家在各自開發(fā)不同的業(yè)務(wù)時(shí),不僅要時(shí)刻和他人同步項(xiàng)目變化、讀懂他人代碼,還要每次編譯完整個(gè)項(xiàng)目才能對自己所做的一點(diǎn)修改進(jìn)行調(diào)試,效率低下。

  • 團(tuán)隊(duì)規(guī)模變化

我開始參與蜂鳥商家版 iOS 端的維護(hù)時(shí),之前只有一個(gè)前輩在維護(hù),也就是一個(gè)人獨(dú)立維護(hù)一個(gè) App。然后過了沒多久,他離職去了另一家公司,所以又變成了一個(gè)人獨(dú)立維護(hù)這個(gè) App。這時(shí)候因?yàn)槭仟?dú)立開發(fā),所以也不存在什么太大的問題。但隨著團(tuán)隊(duì)擴(kuò)大,后面陸續(xù)來了幾位同事共同負(fù)責(zé)這個(gè)項(xiàng)目的維護(hù)工作,大家都在同一個(gè)工程上進(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)我開始參與這個(gè)項(xiàng)目的維護(hù)時(shí),這個(gè)項(xiàng)目就已經(jīng)是一個(gè) Swift 和 OC 混編的項(xiàng)目了,然后還有 RN 和 H5 代碼,可以說是十分復(fù)雜了。雖然這不是我廠唯一一個(gè) Swift 和 OC 的混編項(xiàng)目,但絕對是當(dāng)時(shí) Swift 化最高的一個(gè)項(xiàng)目,約 25% 的代碼為 Swift。眾所周知,Swift 和 OC 的互相調(diào)用遠(yuǎn)不如 Java 和 Kotlin 的互相調(diào)用那么順滑(反正你現(xiàn)在知道了),并且處處藏著危機(jī),暗坑無數(shù),所以迫切需要找一個(gè)方式,將 Swift 和 OC 代碼進(jìn)行整理、轉(zhuǎn)換或者分隔。畢竟,這個(gè)文件是 OC 下一個(gè)文件就是 Swift 這種頻繁的思維轉(zhuǎn)換在業(yè)務(wù)開發(fā)這種本就十分緊張的場景下,會(huì)使人十分疲憊,不利于開發(fā)工作的順利進(jìn)行。

[圖片上傳中...(image-6e2ada-1551146233940-4)]

<figcaption></figcaption>

2. 怎樣去解決

為了解決以上這些問題,我們曾經(jīng)進(jìn)行過如下一些探索:

  1. 移除無用的第三方庫和資源文件,減少打包時(shí)間:效果不明顯;
  2. 整理并推動(dòng)內(nèi)部 Gitflow 工作流,提高協(xié)作效率:有一些效果,但由于項(xiàng)目過大,日常協(xié)作仍然吃力;
  3. 研究 Swift 編譯時(shí)間優(yōu)化方法,提高編譯效率:發(fā)現(xiàn)增加編譯時(shí)間的都是 Swift 的一些常用語法糖,如果不用的話,嚴(yán)重降低開發(fā)效率,遂放棄;
  4. 在不拆分主工程的情況下,推動(dòng)項(xiàng)目整個(gè) Swift 化:由于之前維護(hù)項(xiàng)目的前輩離職,導(dǎo)致目前的項(xiàng)目開發(fā)人員都對原代碼不是十分熟悉,不敢妄加改動(dòng),加之業(yè)務(wù)迭代頻繁,開發(fā)和測試資源都十分緊張,該工作工作推進(jìn)十分緩慢。

可以發(fā)現(xiàn)上述嘗試的結(jié)果都不是十分理想,在與 iOS 組內(nèi)大佬們進(jìn)行一些溝通,聽取大佬們的意見后,決定對原項(xiàng)目進(jìn)行「組件化 / 模塊化拆分」工作,它能帶來如下這些好處:

  • 加快編譯速度,不用再編譯組件 / 模塊外沒有被依賴到的代碼;
  • 便于將每個(gè)模塊指定給不同負(fù)責(zé)人進(jìn)行管理;
  • 降低合并難度,減小沖突和出錯(cuò)概率,提高業(yè)務(wù)開發(fā)效率;
  • 將 Swift 和 OC 代碼進(jìn)行分離,便于進(jìn)一步 Swift 化工作的推進(jìn);
  • 可為模塊編寫單元測試,提高工作效率,同時(shí)方便測試人員進(jìn)行有針對性的測試。

二. 目標(biāo)設(shè)定

  • 功能組件獨(dú)立:保證所有的底層功能組件從主工程抽出,獨(dú)立與主工程之外,便于復(fù)用、業(yè)務(wù)模塊的調(diào)用;
  • 業(yè)務(wù)模塊劃分與拆解:將業(yè)務(wù)按對應(yīng)用途進(jìn)行劃分和拆解,想辦法切斷各業(yè)務(wù)之間的強(qiáng)依賴;
  • 所有組件 / 模塊獨(dú)立編譯:所有功能組件和業(yè)務(wù)模塊能夠獨(dú)立于主工程進(jìn)行編譯,有各自的 Demo 工程;
  • CocoaPods 發(fā)布:在內(nèi)網(wǎng) GitLab 進(jìn)行發(fā)布,并且之后對每個(gè)模塊用 GitFlow 工作流進(jìn)行管理和后續(xù)發(fā)布工作。

[圖片上傳中...(image-f99687-1551146233940-3)]

<figcaption></figcaption>

三. 計(jì)劃制定

說到組件化 / 模塊化,那么什么是組件化 / 模塊化呢?組件化和模塊化的區(qū)別又在哪里呢?

組件,就是我們對功能的封裝,一個(gè)功能就是一個(gè)組件,數(shù)據(jù)庫、網(wǎng)絡(luò)、文件操作、社會(huì)化分享等等這些功能都是組件。我們之所以要搞出組件的概念,是為了能夠讓我們的上層業(yè)務(wù)模塊能夠隨時(shí)依賴和調(diào)用這些基礎(chǔ)功能。組件基本上可以分為基礎(chǔ)功能組件、通用 UI 組件、基礎(chǔ)業(yè)務(wù)組件等這幾類。所以為了滿足上述要求,組件必須具有較高的獨(dú)立性、擴(kuò)展性以及復(fù)用性。

模塊,就是對一系列有內(nèi)聚性的業(yè)務(wù)進(jìn)行整理,將其與其它業(yè)務(wù)進(jìn)行切割、拆分,從主工程或原所在位置抽離為一個(gè)相對獨(dú)立的部分。僅僅針對業(yè)務(wù)而言,比如說我們可以把訂單業(yè)務(wù)獨(dú)立為為一個(gè)模塊,可以把個(gè)人中心獨(dú)立為一個(gè)模塊,把用戶登錄獨(dú)立為一個(gè)模塊等,在 App 中的體現(xiàn)就是一個(gè)個(gè)獨(dú)立的 Git 倉庫。模塊化的一個(gè)好處是用到時(shí)可以搭積木,比如可以多個(gè)工程間復(fù)用同一個(gè)或幾個(gè)業(yè)務(wù)模塊,比如騰訊的 QQ 和 TIM,除了 UI 界面外 TIM 顯然復(fù)用了大量現(xiàn)有的原 QQ 工程的業(yè)務(wù)模塊代碼,當(dāng)然,我們這里暫時(shí)并沒有這個(gè)需求。

經(jīng)過小組會(huì)議討論,我們的想法是將共用組件獨(dú)立出來,然后直接按業(yè)務(wù)對現(xiàn)有主工程進(jìn)行拆分同時(shí)兼顧 Swift 與 OC 分離,大致劃分如下表所示:

1. 組件

組件 庫名 主要內(nèi)容
基礎(chǔ)(OC) LPDBOCFoundationGarbage 基礎(chǔ)的 OC 組件,各種零散的、混亂的視圖、組件、控件、常量、OC 宏定義等,全放在這里,供上層調(diào)用。和他的庫名一樣,其本質(zhì)就大概就是個(gè)垃圾桶。
基礎(chǔ)(Swift) LPDBPublicModule 基礎(chǔ)的 Swift 組件,包含一些公用的 Swift 擴(kuò)展,和模塊間解耦的協(xié)議。
網(wǎng)絡(luò)(OC) LPDBNetwork 網(wǎng)絡(luò)組件,對 AFNetworking 的淺層封裝,同時(shí)包含了和網(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 用戶中心模塊,包含和用戶個(gè)人中心以及狀態(tài)相關(guān)的資源文件、UI、業(yè)務(wù)邏輯代碼等。
... ... ...

3. 關(guān)系

按照上面的思路,理想化的模塊 / 組件依賴關(guān)系圖大概是這個(gè)樣子的:

[圖片上傳中...(image-87a331-1551146233940-2)]

<figcaption></figcaption>

因?yàn)榉澍B商家版的團(tuán)隊(duì)開發(fā)人員之前均沒有過任何項(xiàng)目的拆分經(jīng)驗(yàn),大家也都是摸著石頭過河,走一步看一步。所以雖然以上的拆分思路總體是對的,先拆組件后拆業(yè)務(wù),但由于各種各樣的原因,一些問題就在接下來的工作實(shí)施過程中暴露了出來。

四. 工作實(shí)施

我們小組主要還是以業(yè)務(wù)開發(fā)為主,所以組件化 / 模塊化工作都是大家抽空閑時(shí)間來完成,并沒有進(jìn)行硬性的排期和設(shè)置 Deadline。按照之前制定的計(jì)劃,我們進(jìn)行了以下這些工作:

1. 功能組件獨(dú)立

1.1 LPDBOCFoundationGarbage

LPDBOCFoundationGarbage 是我們項(xiàng)目最先抽出的部分,這個(gè)庫將和 LPDBPublicModule 一起,作為整個(gè)工程的最底層,再往下就是。這個(gè)庫的定位和它的名字一樣,就是一個(gè)垃圾桶,啥都往里放。其中大致包含以下一些東西:

  • 自定義的 View 和控件,例如:小紅點(diǎn)控件、刷新控件、加載控件、Tips 視圖等;
  • 自定義的 Controller,例如:基礎(chǔ)控制器 BaseViewController、WebView 基礎(chǔ)控制器 BaseWebViewController、自定義的彈框 AlertController等;
  • 和業(yè)務(wù)相關(guān)的對基本類型或系統(tǒng)控件的擴(kuò)展:對 NSObject、UIButton、UIImageView、UILabel 等添加的擴(kuò)展代碼 category;
  • 甚至版本控制模塊 LPDBVersionManager 也放在了這里。

因?yàn)槲覀冊谶M(jìn)行拆分任務(wù)的同時(shí),還在同時(shí)維持著項(xiàng)目的開發(fā)工作,所以我們暫時(shí)沒有精力做細(xì)致的拆分工作,只能先把這些零散的部分先放在一起進(jìn)行管理。

1.2 LPDBPublicModule

LPDBPublicModule 是基礎(chǔ)的 Swift 組件,這個(gè)庫主要包含:

  • 一些公用的 Swift 擴(kuò)展,例如:對 CGFloat、Date、NSString 等系統(tǒng)類型的 extension;
  • 用于模塊間解耦的協(xié)議。

因?yàn)楣こ虄?nèi)的 Swift 代碼大多是我們新寫的,所以相對舊的 OC 代碼而言,整理地更好一些,所以這個(gè)倉庫干凈很多

1.3 LPDBNetwork

LPDBNetwork 網(wǎng)絡(luò)組件是我們項(xiàng)目完成 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ò)請求失敗的提示等。

這一部分的話,因?yàn)槎际潜容^古老的代碼,所以當(dāng)初的開發(fā)人員都已經(jīng)不再繼續(xù)維護(hù)了,所以在只能是我們自己進(jìn)行拆分的情況下,為了防止大的變更導(dǎo)致發(fā)生問題,所以沒有對這一塊進(jìn)行更細(xì)致的拆解工作。畢竟再爛代碼也比不能工作的代碼要好。

1.4 LPDBUIKit

Swift 的 UI 庫,我們將工程中的一些 Swift 視圖和控件收集到了這個(gè)項(xiàng)目中,主要包含以下這些內(nèi)容:

  • 視圖,例如:LPDBEmptyDataView、SlideScrollView 等;
  • 控件,例如:SlideTabKit 等。

因?yàn)?Swift 代碼總量還不是很大,所以這個(gè)庫的東西目前也不是很多,以后會(huì)逐漸豐富起來。

2. 業(yè)務(wù)模塊拆分

完成了上面的組件庫的獨(dú)立工作后,業(yè)務(wù)模塊的拆解就相對輕松一些了,目前我們主要完成了三個(gè)業(yè)務(wù)模塊的拆分工作。

2.1 LPDBHistoryModule

LPDBHistoryModule 歷史訂單模塊,和歷史訂單頁面相關(guān)的信息都在該模塊中,主要包含以下內(nèi)容:

  • UI,例如:歷史訂單界面、歷史訂單列表 Cell、加載視圖等;
  • 數(shù)據(jù)模型,例如:歷史訂單模型;
  • 歷史訂單列表相關(guān)的網(wǎng)絡(luò)請求。

因?yàn)樵撃K相對來說比較獨(dú)立,所以拆分過程也比較順利,主要依賴了 LPDBPublicModule、LPDBNetwork、LPDBOCFoundationGarbage 組件。

2.2 LPDBLoginModule

LPDBLoginModule 用戶登錄模塊是一個(gè)與用戶登錄、注冊以及用戶登錄信息有關(guān)的模塊,主要包含了以下信息:

  • UI,例如:用戶登錄界面、用戶注冊界面等;
  • 數(shù)據(jù)模型,例如:用戶信息模型、用戶信息地址模型等;
  • 登錄與注冊相關(guān)的網(wǎng)絡(luò)請求。

該模塊相比較歷史訂單模塊復(fù)雜了一些,不過仍然比較順利,主要依賴了 LPDBPublicModule、LPDBOCFoundationGarbage、LPDBNetwork 組件。

2.3 LPDBUserCenterModule

LPDBUserCenterModule 用戶中心模塊是一個(gè)與用戶個(gè)人中心以及用戶信息修改有關(guān)的模塊,主要包含了以下信息:

  • UI,例如:用戶中心界面、用戶電話修改界面、用戶密碼修改界面等;
  • 數(shù)據(jù)模型,例如:用戶詳細(xì)信息模型、用戶信息地址模型等;
  • 用戶中心相關(guān)的網(wǎng)絡(luò)請求,例如:修改電話號碼、請求驗(yàn)證碼等。

該模塊主要依賴了 LPDBOCFoundationGarbage 組件和 LPDBLoginModule 模塊。

2.4 其它

剩下的其他一些模塊仍然處于計(jì)劃中的狀態(tài),暫未進(jìn)行拆分。到這一步的話,庫間依賴關(guān)系大致如下圖所示:

[圖片上傳中...(image-d579c7-1551146233939-1)]

<figcaption></figcaption>

可以看到其中存在一些不太合理的依賴關(guān)系,如 LPDBUserCenterModule 依賴 LPDBLoginModule 模塊,也就是所謂的業(yè)務(wù)模塊橫向依賴問題,接下來,我們就要處理這一問題。

3. 解除耦合

由于之前開發(fā)過程中從未有過任何模塊化的考量,所以蜂鳥商家版的代碼非常雜糅,項(xiàng)目依賴關(guān)系十分復(fù)雜,主要可以分為以下三類耦合:

  • 界面耦合:App 執(zhí)行過程中,硬編碼的界面間的跳轉(zhuǎn)行為;
  • 工程耦合:某些模塊在運(yùn)行時(shí)需要依賴主工程的代碼才能運(yùn)行或?qū)崿F(xiàn)完整的功能;
  • 依賴耦合:兩個(gè)業(yè)務(wù)模塊之間的有依賴。

3.1 模塊間組件共用

在拆分業(yè)務(wù)模塊的過程中,經(jīng)常發(fā)生兩個(gè)業(yè)務(wù)模塊同時(shí)引用某一塊業(yè)務(wù)代碼的問題,這時(shí)我們就需要對這一塊代碼進(jìn)行理解,首先區(qū)分它到底應(yīng)不應(yīng)該劃分到業(yè)務(wù)層來?

  • 如果是的話,應(yīng)該劃歸到哪一個(gè)模塊中去更合理一些;
  • 如果不是的話,應(yīng)該將這一部分代碼下沉到哪一個(gè)組件庫中去比較合適,或者獨(dú)立為一個(gè)組件。

在 LPDBUserCenterModule 的抽離過程中就遇到了這個(gè)問題,LPDBUserCenterModule 和 LPDBLoginModule 共同依賴了幾個(gè)和用戶信息有關(guān)的數(shù)據(jù)模型,導(dǎo)致需要發(fā)生模塊間橫向依賴,所以我們將共用的數(shù)據(jù)模型抽出,然后下沉到了 LPDBOCFoundationGarbage 中。

3.2 模塊間耦合

另一個(gè)經(jīng)常遇到的問題就是跨模塊調(diào)用代碼的問題了,不僅是模塊與模塊間代碼的互相調(diào)用、模塊間頁面的跳轉(zhuǎn),還有模塊反向調(diào)用主工程代碼等問題,這個(gè)問題的解決我們分了三步:

  • 反射調(diào)用

因?yàn)楣こ痰膹?fù)雜性和以前代碼的不規(guī)范,導(dǎo)致我們在處理切割業(yè)務(wù)模塊時(shí)比較痛苦,所以我們在剛開始抽出模塊時(shí)采用了一種快速但不太安全的方式進(jìn)行解耦,比如在 LPDBUserCenterModule 模塊中需要調(diào)用主工程的 getMiddlePageVC 方法時(shí),我們用了如下臨時(shí)解決方案:

if ([[UIApplication sharedApplication].delegate respondsToSelector:@selector(getMiddlePageVC)]) {
    UIViewController *info = [[UIApplication sharedApplication].delegate performSelector:@selector(getMiddlePageVC)];

    ...
}
復(fù)制代碼

然后在主工程的 中實(shí)現(xiàn)這個(gè)接口:

// .h
@interface AppDelegate : UIResponder <UIApplicationDelegate>

...
// LPDBUserCenterModule
- (UIViewController *)getMiddlePageVC;

...

@end

// .m
@implementation AppDelegate

...

- (UIViewController *)getMiddlePageVC {
    ...

    return xxx;
}

...

@end
復(fù)制代碼

這一方案的優(yōu)點(diǎn)就是靈活,利用 NSClassFromString、performSelector 等方式,能夠快速解決各種耦合問題,瞬間切割出模塊。但缺點(diǎn)也顯而易見,字符串硬編碼,維護(hù)成本大,去掉了編譯器檢查,容易翻車。

  • 協(xié)議調(diào)用

所以自然而然地,當(dāng)我們的某個(gè)業(yè)務(wù)模塊的拆分工作基本定型時(shí),我們就開始將第一步中的反射調(diào)用方式替換為協(xié)議的方式進(jìn)行調(diào)用,比如當(dāng) LPDBLoginModule 模塊需要調(diào)用主工程的 getCoordinate 方法時(shí),示例如下:

id delegate = [[UIApplication sharedApplication] delegate];

if (![delegate conformsToProtocol:@protocol(AppDelegateProtocol)]) {
    return;
}
CLLocationCoordinate2D coordinate = [delegate coordinate];
復(fù)制代碼

然后在主工程中實(shí)現(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)用方做了判斷加上了容錯(cuò),并不能在編譯期就讓開發(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 項(xiàng)目中 H5 頁面的正常跳轉(zhuǎn),提供了此種調(diào)用
LotusootRouter.open(url: "newproj://account/login?username=zhoulingyu").completion { (error) in
    print(error ?? "open success")
}
復(fù)制代碼

具體可以參見 iOS 靈活的 模塊化/組件化 工具與規(guī)范 Lotusoot 解說 一文,在此不多做贅述。類似的工具還有 BeeHiveLPDMvvmRouterKit 等,大家可以自行進(jìn)一步探索。

最終結(jié)構(gòu)就變成了如圖所示的樣子:

[圖片上傳中...(image-e02232-1551146233939-0)]

<figcaption></figcaption>

五. 問題整理

1. 不合理的分層結(jié)構(gòu)和庫間依賴

由于參與拆分工作的人員比較缺乏組件化經(jīng)驗(yàn),所以導(dǎo)致某些庫的拆分不是十分合理,某些應(yīng)該沉入底層的公用 Model 和常量等沒有在開始時(shí)就放到一個(gè)合理的位置。業(yè)務(wù)模塊之間也存在一些不合理的橫向依賴,沒有進(jìn)行一個(gè)合理的業(yè)務(wù)邊界劃分。這些原因?qū)е挛覀冊谶M(jìn)行拆分工作時(shí)經(jīng)常需要回過頭來對已經(jīng)拆出來的模塊和組件重新進(jìn)行整理和處理,重復(fù)勞動(dòng)量很大。

2. 拆分粒度不適中

某些庫比如 LPDBOCFoundationGarbage 比較龐大,而像 LPDBUIKit 這樣的庫中內(nèi)容卻非常少,這一點(diǎn)的處理上存在問題。如果一個(gè)拆分完成的庫仍然比較臃腫的化,說明仍然存在細(xì)化拆分的必余地。

3. 工作進(jìn)度難以控制

由于沒有能提前制定好詳細(xì)的進(jìn)度計(jì)劃表,加上業(yè)務(wù)工作的擠壓,導(dǎo)致我們花在組件化 / 模塊化工作上的時(shí)間比較零散。本意是希望大家能夠靈活安排工作,合理處置業(yè)務(wù)開發(fā)與技術(shù)改造工作之間的關(guān)系,但效果不是很理想,表現(xiàn)就是組件化 / 模塊化工作的進(jìn)行沒有連續(xù)性,大家的積極性和工作效率也都不高。

六. 經(jīng)驗(yàn)總結(jié)

1. 工作開始前要進(jìn)行技術(shù)調(diào)研

查看和學(xué)習(xí)一些同類成功的案例資料或者向業(yè)內(nèi)大佬們請教能夠?qū)τ?jì)劃的制定帶來便利,能夠使我們避免很多錯(cuò)誤的設(shè)計(jì),少走一些彎路,降低返工率。

2. 制定詳細(xì)整體規(guī)劃

在準(zhǔn)備作戰(zhàn)時(shí),我常常發(fā)現(xiàn)定好的計(jì)劃沒有用處,但計(jì)劃的過程仍必不可少。—— 德懷特·艾森豪威爾

制定詳細(xì)的整體規(guī)劃能夠在設(shè)計(jì)階段就將一些不合理的地方暴露出來,從而拿出解決方案使問題提前得到解決,或者把不合理的內(nèi)容刪減替換掉,例如分層不合理、庫間依賴這樣的問題,就會(huì)減少很多。拿出細(xì)致的任務(wù)拆分計(jì)劃和工作量預(yù)估,也能更合理地將任務(wù)安排到開發(fā)人員手中,在提升工作效率的同時(shí)也能盡量避免和業(yè)務(wù)開發(fā)產(chǎn)生沖突。

3. 注意對代碼質(zhì)量的控制

好的代碼和編碼習(xí)慣能夠大幅提升項(xiàng)目的可維護(hù)性,為之后的工作帶來便利。我們之前舊的 OC 代碼比較混亂,基本處于無法維護(hù)的狀態(tài),拆分起來十分痛苦;而新寫的 Swift 代碼明顯質(zhì)量要高很多(這真的不是我們自夸...),拆分起來就順利多了。

4. 重視信息的文檔化

每一個(gè)拆分出的模塊及時(shí)添加文檔,嫌麻煩的話至少要建立一份通用的 README 模板,每一個(gè)模塊或組件的建立者把模塊內(nèi)容、拆分目的、設(shè)計(jì)思路等基本信息記錄一下,有什么坑或者注意點(diǎn)也可以文檔化,使以后的長期項(xiàng)目維護(hù)成為可能。

七. 開源成果

我們在組件化 / 模塊化工作期間,產(chǎn)出的一些庫和工具放在了 GitHub 上進(jìn)行開源,給大家一些借鑒的同時(shí),也希望能夠收到大家的意見和建議,提高我們項(xiàng)目本身的質(zhì)量:

庫名 簡介 倉庫地址
EFPodsAnalyzer 可視化 Pods 庫依賴分析工具 github.com/EyreFree/EF…
EFAutoScrollLabel 一個(gè)帶跑馬燈效果的 UILabel github.com/EyreFree/EF…
Bamboots 一個(gè)面向協(xié)議的 Swift 網(wǎng)絡(luò)庫 github.com/mmoaay/Bamb…
Lotusoot 靈活的 Swift 組件解耦和通信工具 github.com/Vegetarians…
bigkeeper 一個(gè) iOS & Android 模塊化項(xiàng)目效率提升工具 github.com/BigKeeper/b…
SideNavigation 一個(gè)支持側(cè)滑且可自定義的側(cè)邊欄 github.com/CNKCQ/SideN…
ViewPagers 一個(gè)支持手勢的 Segmented Control github.com/CNKCQ/ViewP…

八. 后記

本文基本描述了蜂鳥商家版 App 到目前為止的組件化 / 模塊化實(shí)踐情況,希望本文能夠給您的移動(dòng)項(xiàng)目演進(jìn)提供一些借鑒。在此過程中我們產(chǎn)出的一些文章、開源庫和工具,也希望能給大家?guī)硪欢ǖ膸椭蛘邌l(fā)。歡迎大家提出各種反饋和建議或,幫助我們繼續(xù)改進(jìn)和提高。

2017 年底,也就是差不多我參與蜂鳥商家版的維護(hù)工作滿一年的樣子,由于業(yè)務(wù)調(diào)整的原因這個(gè) App 已經(jīng)移交給別的團(tuán)隊(duì)進(jìn)行維護(hù)了,導(dǎo)致項(xiàng)目的 Swift 化和組件化 / 模塊化工作并沒有全部完成,這一點(diǎn)有些遺憾。不過還是希望蜂鳥商家版能夠越來越好,繼續(xù)為廣大商家朋友們服務(wù)。

好消息是,接下來我主要參與蜂鳥團(tuán)隊(duì)版 App 的架構(gòu)工作,這一次我們根據(jù)之前暴露出的問題制定了詳細(xì)的工作計(jì)劃,有了蜂鳥商家版的踩坑經(jīng)驗(yàn)后,我相信這一次我們一定能順利完成目標(biāo)。2018,加油,一起拼!

本文編寫過程中參考了以下文章,在此對原作者們表示感謝:

  1. 即時(shí)配送網(wǎng)之于外賣O2O,配送的更高境界是社群經(jīng)營
  2. 談?wù)勎业睦斫?組件化/模塊化
  3. 蘑菇街 App 的組件化之路
  4. 豆瓣App的模塊化實(shí)踐
  5. 手機(jī)天貓解耦之路
  6. 京東iOS客戶端組件管理實(shí)踐

九. 后記的后記

「模塊化日常」系列短文,把自己模塊化過程中的踩坑歷程分享出來,給有(或者還沒有)遇到類似問題的同學(xué)一個(gè)參考和幫助:

未完待續(xù)...2333

作者:EyreFree
鏈接:https://juejin.im/post/5a620cf5f265da3e36415764
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 每當(dāng)夜深人靜的時(shí)候 往事便涌上心頭 兒時(shí)與伙伴光著腳牙 偶爾打打架 那些人現(xiàn)在 在哪兒 在離這兒很遠(yuǎn)的地方 那是日...
    一塵九九閱讀 387評論 0 2
  • 感受1 隨著閱讀的逐漸深入,竟有了懵懂的感覺,這本書劃列重點(diǎn)的指南是媽媽們經(jīng)常問作者的問題,所以是為媽媽們寫的...
    快樂的老三閱讀 1,085評論 0 1
  • 在前幾次給大家介紹了幾款沙拉和沙拉醬后,今天給大家分享一款西餐中會(huì)經(jīng)常用到的湯類:棕色小牛高湯 分量:一升 準(zhǔn)備...
    陳小羽321閱讀 1,036評論 0 0
  • 為了換件大衣還失眠的我真是沒誰了。 老早之前就想買件大衣,在小紅書上來來回回看了無數(shù)次,可惜總是金錢和款式?jīng)]有看中...
    寶藍(lán)七七閱讀 458評論 0 0