零. 前言
“蜂鳥配送商家版”是一款針對商家打造的專業配送軟體,有了這款應用,您可以使用蜂鳥商家版呼叫所有平臺訂單及電話訂單配送,餐飲、鮮花、蛋糕、生鮮、商超均可配送。超低運費,清晰合理。海量補貼,充值返現。
以上這段對「蜂鳥商家版」的描述摘自 蜂鳥配送官網,大概可以理解為蜂鳥商家版是一個給廣大商家用來發單呼叫配送員的 App。許多同學可能只聽說過「餓了麼」外賣應用,但是對支撐起外賣配送的後勤業務「蜂鳥配送」卻知之甚少,實際上每天海量的外賣訂單都是由蜂鳥配送系統進行處理和配送最終送到消費者手中的。外賣 O2O 是由外賣平臺、商戶、配送系統這三方合作共同完成的,缺一不可。O2O 最核心的價值就是人與服務的連線,而這種連線最終都是通過配送才得以實現的。
自 2016 年底開始我參與蜂鳥商家版的維護工作,除了日常的開發迭代以外,期間還參與推進了專案 Swift 化、專案元件化 / 模組化、非業務元件開源化等技術改造工作,今天這篇文章就給大家分享一下蜂鳥商家版 iOS 的元件化 / 模組化實踐過程和自己的心得體會。
一. 背景分析
蜂鳥商家版 iOS 端程式碼使用 Git 進行管理,程式碼託管在內網的 GitLab 上。專案的依賴管理工具是大家比較熟悉的 CocoaPods,除了 RN 模組為了和 Android 組公用採用 Submodule 進行管理外,其他所有的子模組都採用 Pods 庫的方式引入。
1. 存在的問題
在「蜂鳥商家版 iOS 元件化 / 模組化」工作開展之前,專案主要存在如下這些問題:
- 專案臃腫不堪
在元件化 / 模組化之前,蜂鳥商家版 App 的所有程式碼 / 資原始檔等都是在同一個主工程裡的,只有 RN 倉庫或組內公用私有庫等極少部分程式碼遊離於主工程之外,所以在開發時,每一次都要編譯整個專案的所有程式碼,十分低效。這個問題在獨立開發時還不是十分明顯,畢竟雖然專案大但是程式碼只有一個人在提交,所以專案程式碼量增加也不是那麼誇張而且對專案發生的變化比較熟悉。但是當多人協作開發時,這個缺陷就暴露了出來,大家在各自開發不同的業務時,不僅要時刻和他人同步專案變化、讀懂他人程式碼,還要每次編譯完整個專案才能對自己所做的一點修改進行除錯,效率低下。
- 團隊規模變化
我開始參與蜂鳥商家版 iOS 端的維護時,之前只有一個前輩在維護,也就是一個人獨立維護一個 App。然後過了沒多久,他離職去了另一家公司,所以又變成了一個人獨立維護這個 App。這時候因為是獨立開發,所以也不存在什麼太大的問題。但隨著團隊擴大,後面陸續來了幾位同事共同負責這個專案的維護工作,大家都在同一個工程上進行業務開發,經常遇到如程式碼衝突、開發效率低下、職責劃分不清、程式碼管理混亂等問題。
- 業務發展壓力
由於公司處在高速發展的階段,業務增長很快,最直觀的表現就是市場 &
客服部門不斷接到大量一線使用者的使用反饋或訴求,最後就變成了產品展示給我們開發人員的一份接一份的 PRD。緊湊的業務開發需求和各種靈活的功能迫使我們想盡一切能夠使用的辦法來提高開發效率,提高提測質量。
- 程式碼管理混亂
當我開始參與這個專案的維護時,這個專案就已經是一個 Swift 和 OC 混編的專案了,然後還有 RN 和 H5 程式碼,可以說是十分複雜了。雖然這不是我廠唯一一個 Swift 和 OC 的混編專案,但絕對是當時 Swift 化最高的一個專案,約 25% 的程式碼為 Swift。眾所周知,Swift 和 OC 的互相呼叫遠不如 Java 和 Kotlin 的互相呼叫那麼順滑(反正你現在知道了),並且處處藏著危機,暗坑無數,所以迫切需要找一個方式,將 Swift 和 OC 程式碼進行整理、轉換或者分隔。畢竟,這個檔案是 OC 下一個檔案就是 Swift 這種頻繁的思維轉換在業務開發這種本就十分緊張的場景下,會使人十分疲憊,不利於開發工作的順利進行。
2. 怎樣去解決
為了解決以上這些問題,我們曾經進行過如下一些探索:
- 移除無用的第三方庫和資原始檔,減少打包時間:效果不明顯;
- 整理並推動內部 Gitflow 工作流,提高協作效率:有一些效果,但由於專案過大,日常協作仍然吃力;
- 研究 Swift 編譯時間優化方法,提高編譯效率:發現增加編譯時間的都是 Swift 的一些常用語法糖,如果不用的話,嚴重降低開發效率,遂放棄;
- 在不拆分主工程的情況下,推動專案整個 Swift 化:由於之前維護專案的前輩離職,導致目前的專案開發人員都對原始碼不是十分熟悉,不敢妄加改動,加之業務迭代頻繁,開發和測試資源都十分緊張,該工作工作推進十分緩慢。
可以發現上述嘗試的結果都不是十分理想,在與 iOS 組內大佬們進行一些溝通,聽取大佬們的意見後,決定對原專案進行「元件化 / 模組化拆分」工作,它能帶來如下這些好處:
- 加快編譯速度,不用再編譯元件 / 模組外沒有被依賴到的程式碼;
- 便於將每個模組指定給不同負責人進行管理;
- 降低合併難度,減小衝突和出錯概率,提高業務開發效率;
- 將 Swift 和 OC 程式碼進行分離,便於進一步 Swift 化工作的推進;
- 可為模組編寫單元測試,提高工作效率,同時方便測試人員進行有針對性的測試。
二. 目標設定
- 功能元件獨立:保證所有的底層功能元件從主工程抽出,獨立與主工程之外,便於複用、業務模組的呼叫;
- 業務模組劃分與拆解:將業務按對應用途進行劃分和拆解,想辦法切斷各業務之間的強依賴;
- 所有元件 / 模組獨立編譯:所有功能元件和業務模組能夠獨立於主工程進行編譯,有各自的 Demo 工程;
- CocoaPods 釋出:在內網 GitLab 進行釋出,並且之後對每個模組用 GitFlow 工作流進行管理和後續釋出工作。
三. 計劃制定
說到元件化 / 模組化,那麼什麼是元件化 / 模組化呢?元件化和模組化的區別又在哪裡呢?
元件,就是我們對功能的封裝,一個功能就是一個元件,資料庫、網路、檔案操作、社會化分享等等這些功能都是元件。我們之所以要搞出元件的概念,是為了能夠讓我們的上層業務模組能夠隨時依賴和呼叫這些基礎功能。元件基本上可以分為基礎功能元件、通用 UI 元件、基礎業務元件等這幾類。所以為了滿足上述要求,元件必須具有較高的獨立性、擴充套件性以及複用性。
模組,就是對一系列有內聚性的業務進行整理,將其與其它業務進行切割、拆分,從主工程或原所在位置抽離為一個相對獨立的部分。僅僅針對業務而言,比如說我們可以把訂單業務獨立為為一個模組,可以把個人中心獨立為一個模組,把使用者登入獨立為一個模組等,在 App 中的體現就是一個個獨立的 Git 倉庫。模組化的一個好處是用到時可以搭積木,比如可以多個工程間複用同一個或幾個業務模組,比如騰訊的 QQ 和 TIM,除了 UI 介面外 TIM 顯然複用了大量現有的原 QQ 工程的業務模組程式碼,當然,我們這裡暫時並沒有這個需求。
經過小組會議討論,我們的想法是將共用元件獨立出來,然後直接按業務對現有主工程進行拆分同時兼顧 Swift 與 OC 分離,大致劃分如下表所示:
1. 元件
元件 | 庫名 | 主要內容 |
---|---|---|
基礎(OC) | LPDBOCFoundationGarbage | 基礎的 OC 元件,各種零散的、混亂的檢視、元件、控制元件、常量、OC 巨集定義等,全放在這裡,供上層呼叫。和他的庫名一樣,其本質就大概就是個垃圾桶。 |
基礎(Swift) | LPDBPublicModule | 基礎的 Swift 元件,包含一些公用的 Swift 擴充套件,和模組間解耦的協議。 |
網路(OC) | LPDBNetwork | 網路元件,對 AFNetworking 的淺層封裝,同時包含了和網路相關的業務功能。 |
… | … | … |
2. 模組
模組 | 庫名 | 主要內容 |
---|---|---|
歷史(OC) | LPDBHistoryModule | 歷史訂單模組,包含和歷史訂單相關的資原始檔、UI、業務邏輯程式碼等。 |
登入(OC) | LPDBLoginModule | 使用者登入模組,包含和登入、註冊頁面相關的資原始檔、UI、業務邏輯程式碼等。 |
使用者中心(OC) | LPDBUserCenterModule | 使用者中心模組,包含和使用者個人中心以及狀態相關的資原始檔、UI、業務邏輯程式碼等。 |
… | … | … |
3. 關係
按照上面的思路,理想化的模組 / 元件依賴關係圖大概是這個樣子的:
因為蜂鳥商家版的團隊開發人員之前均沒有過任何專案的拆分經驗,大家也都是摸著石頭過河,走一步看一步。所以雖然以上的拆分思路總體是對的,先拆元件後拆業務,但由於各種各樣的原因,一些問題就在接下來的工作實施過程中暴露了出來。
四. 工作實施
我們小組主要還是以業務開發為主,所以元件化 / 模組化工作都是大家抽空閒時間來完成,並沒有進行硬性的排期和設定 Deadline。按照之前制定的計劃,我們進行了以下這些工作:
1. 功能元件獨立
1.1 LPDBOCFoundationGarbage
LPDBOCFoundationGarbage 是我們專案最先抽出的部分,這個庫將和 LPDBPublicModule 一起,作為整個工程的最底層,再往下就是。這個庫的定位和它的名字一樣,就是一個垃圾桶,啥都往裡放。其中大致包含以下一些東西:
- 自定義的 View 和控制元件,例如:小紅點控制元件、重新整理控制元件、載入控制元件、Tips 檢視等;
- 自定義的 Controller,例如:基礎控制器 BaseViewController、WebView 基礎控制器 BaseWebViewController、自定義的彈框 AlertController等;
- 和業務相關的對基本型別或系統控制元件的擴充套件:對 NSObject、UIButton、UIImageView、UILabel 等新增的擴充套件程式碼 category;
- 甚至版本控制模組 LPDBVersionManager 也放在了這裡。
因為我們在進行拆分任務的同時,還在同時維持著專案的開發工作,所以我們暫時沒有精力做細緻的拆分工作,只能先把這些零散的部分先放在一起進行管理。
1.2 LPDBPublicModule
LPDBPublicModule 是基礎的 Swift 元件,這個庫主要包含:
- 一些公用的 Swift 擴充套件,例如:對 CGFloat、Date、NSString 等系統型別的 extension;
- 用於模組間解耦的協議。
因為工程內的 Swift 程式碼大多是我們新寫的,所以相對舊的 OC 程式碼而言,整理地更好一些,所以這個倉庫乾淨很多
1.3 LPDBNetwork
LPDBNetwork 網路元件是我們專案完成 OC 和 Swift 基礎部分後最先抽出的部分,剛開始我們認為這部分僅僅是單純的業務網路請求操作和對 AFNetworking 的淺層封裝,不包含介面 UI 邏輯等。不過當我們拆解完成後,發現其中還包含了一堆奇怪的東西:
- 對 AFNetworking 的封裝和網路操作的一些定義,例如:LPDBHttpManager、LPDBRequestObject 和 LPDBModel 等;
- UI 操作,例如:等待檢視 LPDBLoadingView 和 網路請求失敗的提示等。
這一部分的話,因為都是比較古老的程式碼,所以當初的開發人員都已經不再繼續維護了,所以在只能是我們自己進行拆分的情況下,為了防止大的變更導致發生問題,所以沒有對這一塊進行更細緻的拆解工作。畢竟再爛程式碼也比不能工作的程式碼要好。
1.4 LPDBUIKit
Swift 的 UI 庫,我們將工程中的一些 Swift 檢視和控制元件收集到了這個專案中,主要包含以下這些內容:
- 檢視,例如:LPDBEmptyDataView、SlideScrollView 等;
- 控制元件,例如:SlideTabKit 等。
因為 Swift 程式碼總量還不是很大,所以這個庫的東西目前也不是很多,以後會逐漸豐富起來。
2. 業務模組拆分
完成了上面的元件庫的獨立工作後,業務模組的拆解就相對輕鬆一些了,目前我們主要完成了三個業務模組的拆分工作。
2.1 LPDBHistoryModule
LPDBHistoryModule 歷史訂單模組,和歷史訂單頁面相關的資訊都在該模組中,主要包含以下內容:
- UI,例如:歷史訂單介面、歷史訂單列表 Cell、載入檢視等;
- 資料模型,例如:歷史訂單模型;
- 歷史訂單列表相關的網路請求。
因為該模組相對來說比較獨立,所以拆分過程也比較順利,主要依賴了 LPDBPublicModule、LPDBNetwork、LPDBOCFoundationGarbage 元件。
2.2 LPDBLoginModule
LPDBLoginModule 使用者登入模組是一個與使用者登入、註冊以及使用者登入資訊有關的模組,主要包含了以下資訊:
- UI,例如:使用者登入介面、使用者註冊介面等;
- 資料模型,例如:使用者資訊模型、使用者資訊地址模型等;
- 登入與註冊相關的網路請求。
該模組相比較歷史訂單模組複雜了一些,不過仍然比較順利,主要依賴了 LPDBPublicModule、LPDBOCFoundationGarbage、LPDBNetwork 元件。
2.3 LPDBUserCenterModule
LPDBUserCenterModule 使用者中心模組是一個與使用者個人中心以及使用者資訊修改有關的模組,主要包含了以下資訊:
- UI,例如:使用者中心介面、使用者電話修改介面、使用者密碼修改介面等;
- 資料模型,例如:使用者詳細資訊模型、使用者資訊地址模型等;
- 使用者中心相關的網路請求,例如:修改電話號碼、請求驗證碼等。
該模組主要依賴了 LPDBOCFoundationGarbage 元件和 LPDBLoginModule 模組。
2.4 其它
剩下的其他一些模組仍然處於計劃中的狀態,暫未進行拆分。到這一步的話,庫間依賴關係大致如下圖所示:
可以看到其中存在一些不太合理的依賴關係,如 LPDBUserCenterModule 依賴 LPDBLoginModule 模組,也就是所謂的業務模組橫向依賴問題,接下來,我們就要處理這一問題。
3. 解除耦合
由於之前開發過程中從未有過任何模組化的考量,所以蜂鳥商家版的程式碼非常雜糅,專案依賴關係十分複雜,主要可以分為以下三類耦合:
- 介面耦合:App 執行過程中,硬編碼的介面間的跳轉行為;
- 工程耦合:某些模組在執行時需要依賴主工程的程式碼才能執行或實現完整的功能;
- 依賴耦合:兩個業務模組之間的有依賴。
3.1 模組間元件共用
在拆分業務模組的過程中,經常發生兩個業務模組同時引用某一塊業務程式碼的問題,這時我們就需要對這一塊程式碼進行理解,首先區分它到底應不應該劃分到業務層來?
- 如果是的話,應該劃歸到哪一個模組中去更合理一些;
- 如果不是的話,應該將這一部分程式碼下沉到哪一個元件庫中去比較合適,或者獨立為一個元件。
在 LPDBUserCenterModule 的抽離過程中就遇到了這個問題,LPDBUserCenterModule和 LPDBLoginModule 共同依賴了幾個和使用者資訊有關的資料模型,導致需要發生模組間橫向依賴,所以我們將共用的資料模型抽出,然後下沉到了 LPDBOCFoundationGarbage 中。
3.2 模組間耦合
另一個經常遇到的問題就是跨模組呼叫程式碼的問題了,不僅是模組與模組間程式碼的互相呼叫、模組間頁面的跳轉,還有模組反向呼叫主工程程式碼等問題,這個問題的解決我們分了三步:
- 反射呼叫
因為工程的複雜性和以前程式碼的不規範,導致我們在處理切割業務模組時比較痛苦,所以我們在剛開始抽出模組時採用了一種快速但不太安全的方式進行解耦,比如在 LPDBUserCenterModule 模組中需要呼叫主工程的 getMiddlePageVC 方法時,我們用瞭如下臨時解決方案:
if ([[UIApplication sharedApplication].delegate respondsToSelector:@selector(getMiddlePageVC)]) {
UIViewController *info = [[UIApplication sharedApplication].delegate performSelector:@selector(getMiddlePageVC)];
...
}複製程式碼
然後在主工程的 中實現這個介面:
// .h@interface AppDelegate : UIResponder <
UIApplicationDelegate>
...// LPDBUserCenterModule- (UIViewController *)getMiddlePageVC;
...@end// .m@implementation AppDelegate...- (UIViewController *)getMiddlePageVC {
... return xxx;
}...@end複製程式碼
這一方案的優點就是靈活,利用 NSClassFromString、performSelector 等方式,能夠快速解決各種耦合問題,瞬間切割出模組。但缺點也顯而易見,字串硬編碼,維護成本大,去掉了編譯器檢查,容易翻車。
- 協議呼叫
所以自然而然地,當我們的某個業務模組的拆分工作基本定型時,我們就開始將第一步中的反射呼叫方式替換為協議的方式進行呼叫,比如當 LPDBLoginModule 模組需要呼叫主工程的 getCoordinate 方法時,示例如下:
id delegate = [[UIApplication sharedApplication] delegate];
if (![delegate conformsToProtocol:@protocol(AppDelegateProtocol)]) {
return;
}CLLocationCoordinate2D coordinate = [delegate coordinate];
複製程式碼
然後在主工程中實現該方法:
// .h#import "AppDelegate.h"@import LPDBLoginModule;
@interface AppDelegate (Protocol) <
AppDelegateProtocol>
@end// .m@implementation AppDelegate (Protocol)- (CLLocationCoordinate2D)getCoordinate {
return self.coordinate;
}@end複製程式碼
但是,樣的改變並不能徹底解決所編寫的模組間互相呼叫的程式碼缺乏編譯器檢查的問題,而僅僅是對呼叫方做了判斷加上了容錯,並不能在編譯期就讓開發人員察覺到問題,一定要進行測試才可以,所以這種方式也不是十分理想。
- Lotusoot 解耦工具
那麼為了徹底解決問題,我們開發和引入了元件通訊和工具
Lotusoot,呼叫方式有下列幾種可供參考:
- 服務呼叫
let lotus = s(AccountLotus.self) let accountModule: AccountLotus = LotusootCoordinator.lotusoot(lotus: lotus) as! AccountLotusaccountModule.login(username: "admin", password: "wow") {
(error) in print(error ?? "")
}複製程式碼
- 短鏈註冊
let error: NSError? = LotusootRouter.register(route: "newproj://account/login") {
(lotusootURL) in accountModule.showLoginVC(username: "admin", password: "wow")
}複製程式碼
- 短鏈呼叫
let param: Dictionary = ["username" : "admin", "password" : "wow"]// 無回撥 LotusootRouter.open(route: "newproj://account/login", params: param)// 有回撥LotusootRouter.open(route: "newproj://account/login", params: param).completion {
(error) in print(error ?? "open success")
}// ⚠️不推薦的用法,用 ?pram0=xxx 這樣的形式導致字串散落在各處,不易管理。// 但為了保證 Hybrid 專案中 H5 頁面的正常跳轉,提供了此種呼叫LotusootRouter.open(url: "newproj://account/login?username=zhoulingyu").completion {
(error) in print(error ?? "open success")
}複製程式碼
具體可以參見 iOS 靈活的 模組化/元件化 工具與規範 Lotusoot 解說 一文,在此不多做贅述。類似的工具還有 BeeHive 和 LPDMvvmRouterKit 等,大家可以自行進一步探索。
最終結構就變成了如圖所示的樣子:
五. 問題整理
1. 不合理的分層結構和庫間依賴
由於參與拆分工作的人員比較缺乏元件化經驗,所以導致某些庫的拆分不是十分合理,某些應該沉入底層的公用 Model 和常量等沒有在開始時就放到一個合理的位置。業務模組之間也存在一些不合理的橫向依賴,沒有進行一個合理的業務邊界劃分。這些原因導致我們在進行拆分工作時經常需要回過頭來對已經拆出來的模組和元件重新進行整理和處理,重複勞動量很大。
2. 拆分粒度不適中
某些庫比如 LPDBOCFoundationGarbage 比較龐大,而像 LPDBUIKit 這樣的庫中內容卻非常少,這一點的處理上存在問題。如果一個拆分完成的庫仍然比較臃腫的化,說明仍然存在細化拆分的必餘地。
3. 工作進度難以控制
由於沒有能提前制定好詳細的進度計劃表,加上業務工作的擠壓,導致我們花在元件化 / 模組化工作上的時間比較零散。本意是希望大家能夠靈活安排工作,合理處置業務開發與技術改造工作之間的關係,但效果不是很理想,表現就是元件化 / 模組化工作的進行沒有連續性,大家的積極性和工作效率也都不高。
六. 經驗總結
1. 工作開始前要進行技術調研
檢視和學習一些同類成功的案例資料或者向業內大佬們請教能夠對計劃的制定帶來便利,能夠使我們避免很多錯誤的設計,少走一些彎路,降低返工率。
2. 制定詳細整體規劃
在準備作戰時,我常常發現定好的計劃沒有用處,但計劃的過程仍必不可少。—— 德懷特·艾森豪威爾
制定詳細的整體規劃能夠在設計階段就將一些不合理的地方暴露出來,從而拿出解決方案使問題提前得到解決,或者把不合理的內容刪減替換掉,例如分層不合理、庫間依賴這樣的問題,就會減少很多。拿出細緻的任務拆分計劃和工作量預估,也能更合理地將任務安排到開發人員手中,在提升工作效率的同時也能儘量避免和業務開發產生衝突。
3. 注意對程式碼質量的控制
好的程式碼和編碼習慣能夠大幅提升專案的可維護性,為之後的工作帶來便利。我們之前舊的 OC 程式碼比較混亂,基本處於無法維護的狀態,拆分起來十分痛苦;而新寫的 Swift 程式碼明顯質量要高很多(這真的不是我們自誇…),拆分起來就順利多了。
4. 重視資訊的文件化
每一個拆分出的模組及時新增文件,嫌麻煩的話至少要建立一份通用的 README 模板,每一個模組或元件的建立者把模組內容、拆分目的、設計思路等基本資訊記錄一下,有什麼坑或者注意點也可以文件化,使以後的長期專案維護成為可能。
七. 開源成果
我們在元件化 / 模組化工作期間,產出的一些庫和工具放在了 GitHub 上進行開源,給大家一些借鑑的同時,也希望能夠收到大家的意見和建議,提高我們專案本身的質量:
庫名 | 簡介 | 倉庫地址 |
---|---|---|
EFPodsAnalyzer | 視覺化 Pods 庫依賴分析工具 | github.com/EyreFree/EF… |
EFAutoScrollLabel | 一個帶跑馬燈效果的 UILabel | github.com/EyreFree/EF… |
Bamboots | 一個面向協議的 Swift 網路庫 | github.com/mmoaay/Bamb… |
Lotusoot | 靈活的 Swift 元件解耦和通訊工具 | github.com/Vegetarians… |
bigkeeper | 一個 iOS & Android 模組化專案效率提升工具 |
github.com/BigKeeper/b… |
SideNavigation | 一個支援側滑且可自定義的側邊欄 | github.com/CNKCQ/SideN… |
ViewPagers | 一個支援手勢的 Segmented Control | github.com/CNKCQ/ViewP… |
八. 後記
本文基本描述了蜂鳥商家版 App 到目前為止的元件化 / 模組化實踐情況,希望本文能夠給您的移動專案演進提供一些借鑑。在此過程中我們產出的一些文章、開源庫和工具,也希望能給大家帶來一定的幫助或者啟發。歡迎大家提出各種反饋和建議或,幫助我們繼續改進和提高。
2017 年底,也就是差不多我參與蜂鳥商家版的維護工作滿一年的樣子,由於業務調整的原因這個 App 已經移交給別的團隊進行維護了,導致專案的 Swift 化和元件化 / 模組化工作並沒有全部完成,這一點有些遺憾。不過還是希望蜂鳥商家版能夠越來越好,繼續為廣大商家朋友們服務。
好訊息是,接下來我主要參與蜂鳥團隊版 App 的架構工作,這一次我們根據之前暴露出的問題制定了詳細的工作計劃,有了蜂鳥商家版的踩坑經驗後,我相信這一次我們一定能順利完成目標。2018,加油,一起拼!
本文編寫過程中參考了以下文章,在此對原作者們表示感謝:
九. 後記的後記
「模組化日常」系列短文,把自己模組化過程中的踩坑歷程分享出來,給有(或者還沒有)遇到類似問題的同學一個參考和幫助:
- 模組化日常:神奇的 pod repo push 失敗
- 模組化日常:CocoaPods 1.4.0 真好用(並不)
- 模組化日常:CocoaPods 庫資源引用問題
- 模組化日常:重名類
- 模組化日常:耗時的釋出
- 模組化日常:開源庫與私有庫重名
- 模組化日常:庫間互相依賴
未完待續…2333
如有任何智慧財產權、版權問題或理論錯誤,還請指正。
juejin.im/post/5a620c…
轉載請註明原作者及以上資訊。