一.組件化介紹
需求來源
隨着項目規模不斷擴大,業務模塊增多,開發過程中會有多條產品線(多人或多小組開發不同的功能);如果用傳統的開發模式,會導致代碼臃腫,編譯速度越來越慢,開發效率低下,代碼維護成本越來越高.
組件化優勢
代碼邏輯和項目結構清晰;代碼利用率高,迭代效率高;可以快速集成,並能做單元測試;每個組件可以單獨運行,組件之間的耦合度低.
組件化模塊劃分
基礎組件: 宏定義/自定義分類/自定義工具類
功能組件: 項目中常用功能,如:定位/推送/分享
業務組件: 根據具體業務而定,如:聊天/商城
中間組件: 負責界面路由/傳參/回調
宿主工程: 類似一個殼子,組合各個組件,形成一個完整的App
組件化實質
組件化其實是把每一個功能模塊拆分成一個一個的Pod庫;比如項目中要用到AFN,只要Pod一下,便觸手可及~;現在我們製作自己的Pod庫,然後把它集成到項目中.
二.需要了解
Trunk賬號
- 認證CocoaPods API的服務
- 用來管理公共倉庫中的自己的組件
索引文件(.podspec文件)
- 記錄一個組件的名稱/版本/資源儲存路徑/維護者信息等
- 每個組件都必須有一個索引文件
索引文件庫(Spec Repo)
- 存放索引文件的倉庫
- 儲存在CocoaPods服務器上,我們下載或更新Pod的時候會把這個倉庫拷貝一份到本地,本地存放路徑:~/.cocoapods/repos/
- CocoaPods提供一個公共庫,儲存在本地的路徑爲:~/.cocoapods/repos/master/
- 我們可以創建私有倉庫,儲存在本地的路徑爲:~/.cocoapods/repos/自定義倉庫名/
組件模板
- CocoaPods提供用於快速創建組件的模板
- 裏邊可以製作我們的代碼,可以做單元測試等,包含一個對應的索引文件
- 組件化就是以這個模板爲基礎,製作自己的組件
三.思路梳理(注意劃重點了)
- 有了以上基礎知識的瞭解我們來梳理一下思路
- 本文會使用私有索引倉庫來維護組件(不使用公共倉庫master)
- 組件添加到公共倉庫中需要註冊Trunk賬號: 傳送門
- 在碼雲(或者其他Git倉庫)創建一個私有的倉庫,當做<私有索引文件倉庫>,後邊用來儲存索引文件(項目名稱:xxSpecs)
- 在碼雲(或者其他Git倉庫)創建一個公開的倉庫,當做<組件倉庫>,後邊用來儲存組件(項目名稱:xxKit)
- CocoaPods服務器不儲存我們的代碼,只儲存索引文件
- 製作好組件之後,索引文件裏會儲存<組件倉庫>的地址,把索引文件傳給CocoaPods服務器,告訴它儲存在指定的<私有索引文件倉庫>
- 使用時,先通過CocoaPods服務器更新<私有索引文件倉庫>到本地;項目中Pod某個組件的時候,會在本地<私有索引文件倉庫>中找到這個組件的索引文件,從索引文件裏拿到<組件倉庫>的地址,從這個地址把代碼下載到項目中
- 總結:思路梳理介紹了組件化製作過程的主幹,只要大體明白我們在幹什麼,下邊具體操作時會有詳細步驟
四.具體操作
索引文件倉庫
- 關聯索引文件倉庫
- 把碼雲上創建的索引文件倉庫關聯拷貝到本地
pod repo add [倉庫名] [倉庫URL地址]
- 之後輸入遠端Git倉庫的賬號和密碼
- 檢查是否安裝成功
- cd 到索引文件倉庫
cd ~/.cocoapods/repos/[倉庫名]
- 驗證索引文件倉庫
pod repo lint .
組件
- 本地新建一個文件夾,用於存放管理組件(起名:xxPod)
- 下載組件模板到xxPod文件夾
- cd 到xxPod文件夾
cd [文件夾全路徑]/xxPod
- 下載組件模板並設置組件名
[組件名] : xxKit (跟碼雲上組件倉庫的名字一致)pod lib create [組件名]
- 組件基本設置
// 使用哪種系統的模板
What platform do you want to use?? [ iOS / macOS ]
> ios
// 使用哪種語言
What language do you want to use?? [ Swift / ObjC ]
> objc
// 是否創建測試Demo
Would you like to include a demo application with your library? [ Yes / No ]
> yes
// 使用哪種測試框架
Which testing frameworks will you use? [ Specta / Kiwi / None ]
> specta
// 是否需要測試視圖
Would you like to do view based testing? [ Yes / No ]
> yes
// 測試Demo的類前綴
What is your class prefix?
> XX
- 代碼製作
- 把自己的代碼(類文件)直接複製到xxPod/xxKit/xxKit/Classes裏
- 配置組件索引文件: 傳送門
- 檢查索引文件格式是否規範
- cd 到組件根目錄
cd [文件夾全路徑]/xxPod/xxKit
- 檢查本地索引文件(passed validation 表示通過驗證;--allow-warnings可忽略警告)
pod lib lint
如果提示標籤類錯誤可暫時不用管,往下繼續
- 製作好的代碼Pod到組件測試工程中(可進行編譯,運行,發現代碼問題)
- cd 到組件的Example文件夾
cd [文件夾全路徑]/xxPod/xxKit/Example
- Pod集成
pod install
- 把做好的組件推送到自己的組件倉庫
- cd 到組件根目錄
cd [文件夾全路徑]/xxPod/xxKit/
- 初始化git
git init
git add .
- 提交一個Git版本
git commit -m "xxKit組件初始化"
- 關聯碼雲上的組件倉庫
git remote add origin [組件倉庫URL]
- 推送版本到master分支(-f強制推送,覆蓋掉之前的所有文件)
git push origin master -f
- 添加版本標籤(標籤號必須與索引文件裏的標籤號一致)
git tag 0.1.0
- 標籤推送到組件倉庫
git push --tags
- 檢查遠程索引文件(passed validation 表示通過驗證)
pod spec lint
關聯CocoaPods服務器
- 製作好的組件關聯CocoaPods服務器
- cd 到xxKit組件根目錄
cd [文件夾全路徑]/xxPod/xxKit
- 推送組件的索引文件到服務器,並告訴服務器存在哪個私有倉庫中
[私有倉庫名] : xxSpecs
[組件名] : xxKitpod repo push [私有倉庫名] [組件名].podspec --allow-warnings
- 查看本地的CocoaPods倉庫(可看到公共庫和自己的私有庫)
pod repo
- 檢查組件
- 更新本地CocoaPods倉庫
pod repo update
- 搜索剛纔製作的組件
[組件名] : xxKitpod search [組件名]
項目中引用私用組件
- 新建一個項目工程,並添加Pod
- 配置Podfile文件
- 全局添加(<私有索引文件倉庫>地址)
source 'https://gitee.com/xxSpecs.git'
- 單獨添加(<組件倉庫>地址)
pod 'xxKit', :git => 'https://gitee.com/xxKit.git'
五.iOS組件化開發架構設計
六.iOS組件化方案探索
一、什麼是組件化?
1、什麼是組件?
"組件"
一般來說用於命名比較小的功能塊,如:下拉刷新組件、提示框組件。而較大粒度的業務功能,我們習慣稱之爲"模塊"
,如:首頁模塊、我的模塊、新聞模塊。
這次討論的主題是組件化,這裏爲了方便表述,下面模塊和組件代表同一個意思,都是指較大粒度的業務模塊。
2、什麼是組件化?
組件化,或者說模塊化,用來分割、組織和打包軟件。每個模塊完成一個特定的子功能,所有的模塊按某種方法組裝起來,成爲一個整體,完成整個系統所要求的功能。
從工程代碼層面來說,組件化的實施通常是通過中間件
解決組件間頭文件直接引用、依賴混亂的問題;從實際開發來說,組件之間最大的需求就是頁面跳轉,需要從組件A的pageA頁面跳轉到組件B的pageB頁面,避免對組件B頁面ViewController頭文件的直接依賴。
二、爲什麼要組件化?
1、組件化是爲了解決什麼問題?
一個 APP 有多個模塊,模塊之間會通信,互相調用,如我們的app,有首頁、行情、資訊、我的等模塊。這些模塊會互相調用,例如 首頁底部需要展示部分資訊、行情;行情底部需要展示個股資訊;資訊詳情頁需要跳轉到行情,等等。
2、組件化的好處?
一般意義:
- 加快編譯速度(不用編譯主客那一大坨代碼了);
- 各組件自由選擇開發姿勢(MVC / MVVM / FRP);
- 組件工程本身可以獨立開發測試,方便 QA 有針對性地測試;
- 規範組件之間的通信接,讓各個組件對外都提供一個黑盒服務,減少溝通和維護成本,提高效率;
對於公司已有項目的現實意義:
- 業務分層、解耦,使代碼變得可維護;
- 有效的拆分、組織日益龐大的工程代碼,使工程目錄變得可維護;
- 便於各業務功能拆分、抽離,實現真正的功能複用;
- 業務隔離,跨團隊開發代碼控制和版本風險控制的實現;
- 模塊化對代碼的封裝性、合理性都有一定的要求,提升開發同學的設計能力;
- 在維護好各級組件的情況下,隨意組合滿足不同客戶需求;(只需要將之前的多個業務組件模塊在新的主App中進行組裝即可快速迭代出下一個全新App)
3、什麼情況下進行組件化比較合適?
當然組件化也有它的缺點:
-
學習成本高,對於開發人員對各種工具的掌握要求也比較高,對於新手來說入門較爲困難。
-
由於工具和流程的複雜化,導致團隊之間協作的成本變高,某些情況下可能會導致開發效率下降。
當項目App處於起步階段、各個需求模塊趨於成熟穩定的過程中,組件化也許並沒有那麼迫切,甚至考慮組件化的架構可能會影響開發效率和需求迭代。
而當項目迭代到一定時期之後,便會出現一些相對獨立的業務功能模塊,而團隊的規模也會隨着項目迭代逐漸增長,這便是中小型應用考慮組件化的時機了。這時爲了更好的分工協作,團隊安排團隊成員各自維護一個相對獨立的業務組件是比較常見的做法。
在這時這個時候來引入組件化方案,是比較合適的時機。長遠來看,組件化帶來的好處是遠遠大於壞處的,特別是隨着項目的規模增大,這種好處會變得越來越明顯
三、如何組件化?
1、如何劃分組件?
- 基礎功能組件
- 基礎產品組件
- 個性化業務組件
對於一個沒有實施過組件化拆分的工程來說,其中很可能充滿了大量不合理的類、方法、頭文件和各種錯亂的依賴關係,因此首先要進行的第一步是模塊拆分。
模塊拆分可以分成兩個部分,基礎模塊拆分和業務模塊拆分。基礎模塊通常是穩定的依賴代碼,業務模塊是涉及到業務的需要頻繁改動的代碼。
基礎模塊拆分
基礎模塊是任何一個App都需要用到的,如:性能統計、Networking、Patch、網絡診斷、數據存儲模塊。對於基礎模塊來說,其本身應該是自洽的,即可以單獨編譯或者幾個模塊合在一起可以單獨編譯。所有的依賴關係都應該是業務模塊指向基礎模塊的。
基礎模塊之間儘量避免產生橫向依賴。
業務模塊拆分
對於業務模塊來說,考慮到舊有代碼可能沒有相關的橫向解耦策略,業務模塊之間的依賴會非常複雜,難以單獨進行拆分,因此我們採用的方法是首先從 group 角度進行重新整理。
對業務量很大的工程來說,我個人更加推薦“業務-分層”這樣的結構,而不是“分層-業務”,即類似下面的 group 結構:
- BusinessA
- Model
- View
- Controller
- Store
- BusinessB
- Model
- View
- Controller
-Store
而非目前項目中採用的:
- Controllers
- BusinessA_Controller
- BusinessB_Controller
- Views
- BusinessA_View
- BusinessB_View
- Models
- BusinessA_Model
- BusinessB_Model
2、組件化的技術難點?
組件化的實施,直觀上看,只是需要將各業務組件的代碼放到各自的文件夾或者 jar包裏就行了。
這裏引出的是:
2.1、組件的拆分方式問題:
可以利用CocoaPods 配合 git 做代碼版本管理,獨立業務模塊單獨成庫。
但這僅僅是物理上拆分了,拆分後的代碼編譯是肯定通不過的,因爲如下:
#import "MainViewController.h"
#import "HomeViewController.h"
#import "NewsViewController.h"
#import "MeViewController.h"
#import ...
@implementation MainViewController
@end
MainViewController
會找不到依賴的其它各個模塊的頭文件而報錯。這裏引出的又是另一個問題:
2.2、組件間如何解耦?
組件間解耦,是組件化必須解決的一個問題。換句話說,就是如何解除業務模塊間的橫向依賴。還是拿上邊舉得例子來說:
App的根視圖MainViewController
需要管理首頁、新聞、我的等等頁面時,如何做到 MainViewController
中,不用去 import
這一大堆 XXViewController
?
很簡單,按軟件工程的思路,下意識就會加一箇中間層Mediator:
這樣一來,各個模塊直接都不需要再互相依賴,而是僅需要依賴 Mediator 層即可。
可直觀上看,這樣做並沒有什麼好處,依賴關係並沒有解除,Mediator 依賴了所有模塊,而調用者又依賴 Mediator,最後還是一坨互相依賴,跟原來沒有 Mediator 的方案相比除了更麻煩點其他沒區別。
我們希望最終能過實現的是單向的依賴,即:
七.組件化細分設計
一.組件化方案中的去model設計
- 組件間調用時,是需要針對參數做去model化的。如果組件間調用不對參數做去model化的設計,就會導致
業務形式上被組件化了,實質上依然沒有被獨立
。 - 假設模塊A和模塊B之間採用model化的方案去調用,那麼調用方法時傳遞的參數就會是一個對象。如果對象不是一個面向接口的通用對象,那麼mediator的參數處理就會非常複雜,因爲要區分不同的對象類型。如果mediator不處理參數,直接將對象以範型的方式轉交給模塊B,那麼模塊B必然要包含對象類型的聲明。假設對象聲明放在模塊A,那麼B和A之間的組件化只是個形式主義。如果對象類型聲明放在mediator,那麼對於B而言,就不得不依賴mediator。對於響應請求的模塊而言,依賴mediator並不是必要條件,因此這種依賴是完全不需要的,這種依賴的存在對於架構整體而言,是一種污染。
- 如果參數是一個面向接口的對象,那麼mediator對於這種參數的處理其實就沒必要了,更多的是直接轉給響應方的模塊。而且接口的定義就不可能放在發起方的模塊中了,只能放在mediator中。響應方如果要完成響應,就也必須要依賴mediator,然而前面我已經說過,響應方對於mediator的依賴是不必要的,因此參數其實也並不適合以面向接口的對象的方式去傳遞。
因此,使用對象化的參數無論是否面向接口,帶來的結果就是業務模塊形式上是被組件化了,但實質上依然沒有被獨立。
在這種跨模塊場景中,參數最好還是以去model化的方式去傳遞,在iOS的開發中,就是以字典的方式去傳遞。這樣就能夠做到只有調用方依賴mediator,而響應方不需要依賴mediator。然而在去model化的實踐中,由於這種方式自由度太大,我們至少需要保證調用方生成的參數能夠被響應方理解,然而在組件化場景中,限制去model化方案的自由度的手段,相比於網絡層和持久層更加容易得多。- 因爲組件化天然具備了限制手段:參數不對就無法調用!無法調用時直接debug就能很快找到原因。所以接下來要解決的去model化方案的另一個問題就是:如何提高開發效率。
- 在去model的組件化方案中,影響效率的點有兩個:調用方如何知道接收方需要哪些key的參數?調用方如何知道有哪些target可以被調用?其實後面的那個問題不管是不是去model的方案,都會遇到。爲什麼放在一起說,因爲我接下來要說的解決方案可以把這兩個問題一起解決。
二.解決方案就是使用category
- mediator這個repo維護了若干個針對mediator的category,每一個對應一個target,每個category裏的方法對應了這個target下所有可能的調用場景,這樣調用者在包含mediator的時候,自動獲得了所有可用的target-action,無論是調用還是參數傳遞,都非常方便。接下來我要解釋一下爲什麼是category而不是其他:
- category本身就是一種組合模式,根據不同的分類提供不同的方法,此時每一個組件就是一個分類,因此把每個組件可以支 持的調用用category封裝是很合理的。
- 在category的方法中可以做到參數的驗證,在架構中對於保證參數安全是很有必要的。當參數不對時,category就提供了補救的入口。
- category可以很輕鬆地做請求轉發,如果不採用category,請求轉發邏輯就非常難做了。
- category統一了所有的組件間調用入口,因此無論是在調試還是源碼閱讀上,都爲工程師提供了極大的方便。
- 由於category統一了所有的調用入口,使得在跨模塊調用時,對於param的hardcode在整個App中的作用域僅存在於category中,在這種場景下的hardcode就已經變成和調用宏或者調用聲明沒有任何區別了,因此是可以接受的。