背景
最近公司主App3.0大升級 , 基本框架及大部分功能會參照另一個已有App, 為了方便升級以及后續兩個App的開發 , 經過開發與產品討論, 決定將已有App進行組件化
方案
組件化其實并不是一件全程明朗的事情, 在最開始定下使用cocoapods作為組件化的工具時, 開發只能根據目前App的功能點及功能間相關邏輯進行了一個簡單的分類 , 并且按照這個分類大致分為3個階段進行提測
組件化操作(參考之前寫過的工具類組件化步驟, 可以跳過)
-
創建一個測試的iOS工程
image.png -
在該目錄下創建一個 文件夾MeishaPod
image.png -
用命令行cd入MeishaPod中 并執行命令
pod lib create MeishaTools
image.png
得到該文件
image.png -
把自已弄好的庫扔進這個弄好的容器中 ,Classes中的replace文件可以刪除掉
image.png -
在命令行中進入Example目錄下
pod install
;然后打開Example中的工程 ,發現庫已經全部進來了,并且是跟剛剛復制進Classes中的文件是一個源的。(既修改這里會改到Classes中的文件)
image.png -
修改這個庫中的信息->打開MieshaTools.podspec。
image.png 要提交上gitlab上面,在我們寫的MeishaTools下面開始進行git操作
git init
git add .
git commit -m '描述'
git remote add origin https://gitlab.meishakeji.com/daniel/meishatools.git
git push origin master
- 接下來進入打tag環節,為什么要打tag呢?因為cocoapods要找到你這個庫必須是通過tag來找的,并且在MieshaTools.podspec中的信息中要更新你的tag;如果不寫,cocoapods發布你的庫時候會找不到你在git上的庫。
可以這么理解 MieshaTools.podspec文件中的s.version
就是 cocoapods要發布的你的庫的git上面的版本 一一對應
git tag 1.0.3
git push --tag
- 這時候我們要上傳到cocoapods了;用命令行進入MeishaTools,然后執行命令
pod lib lint --verbose --use-libraries --allow-warnings
-
--verbose
是查看打印的日志信息 -
--use-libraries
如果庫中使用到framework就需要加上 -
--allow-warnings
是忽略警告
如果顯示passed validation 就是證明編譯通過 那么就可以執行發布了 執行命令
pod trunk push MeishaTools.podspec --verbose --use-libraries --allow-warnings
提交成功后那么這個庫就存在cocoapod上面了-
使用這個庫 要cd進iOS測試工程根目錄中pod init 然后在podfile中導入這個庫
然后繼續再執行命令pod install
就可以再我們工程中使用這個基礎工具類的庫了
image.png
組件化過程
第一階段
- 根據預定目標 , 首先進行項目主框架的組件化 , 這一過程涉及三個點:
1. 項目的基礎工具類 2. 項目的基礎UI類 3.項目的框架結構類
為了增加組件的通用性, 把工具類和基礎UI類另外抽成了兩個組件
image.png
在這一過程中, 由于視頻上傳和視頻轉場工具涉及到FFmpeg, 在進行組件化時, 這兩部分暫時沒有封進組件中(此過程相對比較順利, 因為底層組件及控件一般不涉及業務, 組件化也就簡單得多) - 學生列表模塊/登錄模塊/微主頁模塊的組件化, 這三個模塊除了登錄模塊為了迎合兩個App不同的彈窗邏輯, 做了較大改動, 另外的兩個模塊則十分順利的完成了
第二階段
- 營期相冊/班級課表/班級空間 這三個模塊組件化時, 因為模塊內部沒有與其他模塊的關聯邏輯, 也沒有跳轉邏輯, 因為組件化十分快速順利
- 在班級動態組件化時, 由于第一階段的視頻上傳模塊沒有完成, 因此大部分時都在處理FFmpeg及視頻上傳模塊的組件化
- 成長記錄的數據結構大部分是跟其他組件相關聯, 并且有些處理邏輯也和對應組件有聯動, 因此把成長記錄的組件化放到最后, 等所有的其他模塊組件化完成再考慮這個模塊
此外, 由于成長手冊中使用的轉場工具類是Swift與OC混編, 而在組件內不支持swift類轉oc頭文件使用, 因此在該模塊浪費了大量時間之后決定暫不進行組件化, 直接復制到兩個項目中使用
第三階段
-
通訊錄及聊天模塊處理時, 考慮到聊天的表情包處理相對較獨立, 所以把它獨立成一個小組件, 并且后面發現除了通訊錄模塊需要依賴這個小組件, 在消息首頁的顯示也需要, 剛好減少了后期重新再次拆分的工作量
image.png - 班級通知/學生作業/課程直播, 單個模塊組件化基本按照前面的步驟進行, 把關聯其他模塊的邏輯釋和/耦合嚴重的代碼暫時注釋掉
到這個階段, 基本上大部分的模塊已經組件化完成, 除了消息模塊中涉及大量其他模塊的消息類型, 還有成長記錄涉及其他模塊的入口管理和記錄顯示問題
第四階段(在提測后繼續進行)
-
在成長記錄頁面上, 頂部控件顯示管理著其他各個模塊入口 , 底部列表顯示各種其他模塊類型的記錄數據 , 因此跟其他模塊會有很大的耦合性
image.png
為了解決這個問題, 參考之前看過的路由實現模式, 最后決定采用注冊組件的方式 , 由App進行管理注冊顯示的模塊入口
image.png
在App啟動完成后, 第一時間注冊應用入口, 并且配置相關信息, 包括組件的入口/標題/圖標和應用Id, 同時注冊一個初始化時特殊處理的回調, 方便一些組件的特殊配置; 注冊完成后, 在成長記錄組件內請求接口獲取應用列表時就可以根據注冊的組件map來匹配并展示模塊入口以及跳轉進組件內部
-
在處理底部展示的數據列表時, 由于之前已經考慮過多模塊的情況, 因此成長記錄的數據模型會通過一層中間層把各種模塊的數據模型處理為統一的數據結構,再進行展示; 因此在組件化時, 把中間處理層也封裝為注冊管理對象, 在獲取數據后通過回調, 交給外界來處理
image.png -
除了成長記錄關聯性高, 首頁消息模塊的關聯性也高(班級通知/學生請假/直播通知/教學反饋/作業通知), 并且有更多的組件間跳轉邏輯; 為了統一組件邏輯, 這里也同樣用了跟成長記錄一樣的方式, 通過注冊來管理各種消息類型
image.png
同樣為了解耦和靈活性, 也有一個進入消息詳情時的回調
image.png -
最后一步, 處理每一個組件內部需要跳轉到其他組件的耦合邏輯; 這里有考慮過市場上已經成熟的一些路由第三方庫, 但是大部分都比較復雜并且除了模塊路由還有scheme路由/URI路由等等, 對于我們的項目來說, 只需要用到模塊路由, 因此決定不使用這些庫, 而是自己寫一個路由管理組件之間的邏輯
image.png -
首先在每一個模塊的頭文件中定義好模塊內部需要跳轉其他模塊的跳轉唯一Id
image.png -
然后同樣在app啟動后, 注冊該跳轉Id對應的實際跳轉的位置
image.png -
最后在模塊內部通過跳轉id 跳轉的對應的頁面并傳遞必要數據
image.png
通過這種注冊跳轉Id的方式, 模塊內部只需要告訴外界內部有這么一個跳轉事件, 而不需要知道具體要跳轉的頁面是哪里, 完全交給外界來控制, 從而達到解耦
其他遇到的問題
1. 多架構問題
組件化時經常驗證組件有效性時無法通過, 查看verbose相關信息后, 發現是編譯鏈接i386架構時報錯, i386是mac上32位模擬器的支持架構, 因此直接在.podSpec中配置指定驗證的架構, 去掉i386 s.pod_target_xcconfig = { 'VALID_ARCHS' => 'x86_64 arm64 arm64e armv7' }
2.FFmpeg的取巧組件化
在封裝FFmpeg作為底層組件時, 由于源代碼包含.c .m , 而在組件內不支持引入C++系統模塊, 因此一直會報錯, 最終想出了一個比較繞彎路的方法: 首先新建一個靜態庫工程, 將FFmpeg中包含的.a文件, fdk-aac-ios插件, x264-iOS插件都拖入工程內
再將FFmpeg的工具處理源代碼以及OC管理工具加入工程中
根據報錯信息處理ffmpeg_filter.c中的相關代碼后, 將該工程編譯成一個x86_64架構的模擬器靜態庫, 以及一個arm64架構的真機靜態庫, 并用 lipo工具將它們合并成一個.a多架構靜態庫, 最終放到VSBaseFFmpeg組件中, 完成FFmpeg的組件化
3. 調試與代碼同步
前面也說過, 之所以決定將已有App組件化, 是為了方便梅沙教育的升級以及后續兩個App的開發, 但是cocoapods組件化時 , 如果通過私有索引庫來鏈接到每一個組件的話, 每一個組件都是一個靜態庫, 對于靜態庫內部的代碼是無法修改和調試的; 并且對于組件內部的每一次修改都需要重新提交上傳打Tag, 再發布到索引庫中, 然后才能更新, 不管是多人還是單人開發時都十分麻煩
-
為了解決這兩個問題, 首先采用cocoaPods的本地引用方式來引用模塊, 這樣就可以直接調試組件內部代碼 image.png
查閱資料時發現git本身有子模塊管理邏輯, 并且能做到僅引用子模塊而不需全部下載, 某個子模塊的提交有更新也可以及時提醒并更新該子模塊, 基本上完全能解決現有問題, 所以最后決定不使用cocoapods的三方庫管理模式, 采用git子模塊管理
將兩個以及組件文件夾放到同一個目錄下, 以統一引用組件的的相對路徑 , 然后通過git命令產生子模塊關聯git submodule add [子模塊的git地址] [子模塊名]
最終git父模塊將會依賴于各個git子模塊, .gitmodules 文件會有如下配置信息
在多人開發時, 直接拉取git父模塊就可以擁有兩個項目及關聯的組件庫
-
第一種拉取方式
git clone xxx.git
這樣拉取下來的項目只有目錄, 目錄里面都是空的, 只有配置文件和依賴關系, 需要另外一部子模塊操作
git submodule init
git submodule update
image.png 第二種拉取方式
git clone xx.git --recurse-submodules #遞歸拉取子模塊
我們的項目中有一些文件比較大, 而gitlab服務端的設置有限制, 可能會導致一些組件拉取失敗, 這時候可以用下面這個命令, 讓每一個子模塊執行checkout命令
git submodule foreach git checkout master
最終項目組件結構如下:
項目組件化進行到這里已經基本上成功了, 剩下的是一些比較小的耦合邏輯, 會在之后的版本開發時慢慢解決
總結
- 完成組件化后, 對于之后的版本, 開發起來會變得更加巴適, 代碼管理也會規范, 業務邏輯同樣會更加清晰;
- 組件化過程最大的收獲是對于耦合邏輯的解耦思考, 代碼也變得更加健壯了;
- 這次項目組件化, 是對個人能力的一次巨大提升, 在以后編寫代碼時也會更加多的注意耦合及靈活性;
- 希望能找時間把教師端也組件化了(造福后人)。