抖音 iOS 推荐 Feed 容器化总结
動手點關注干貨不迷路?👆
背景
抖音 Feed 容器在推薦、關注、同城、朋友等多個場景中使用,每個場景都有自身的邏輯和業務,最終匯總在 FeedViewController 中,隨著業務的迭代,代碼越來越臃腫,面臨如下的問題:
- 容器類(FeedViewController) 有 10000+行,還有十多個業務分類,整體的理解和維護成本高 
- 容器類 框架和業務邊界不清晰,框架代碼的修改不收斂和不規范,業務改動可能導致線上問題,如數據層不收斂導致的問題:自動刪除導致一次滑動多個視頻或者自動跳轉到第一個視頻等問題 
- 容器類 承擔了推薦、關注、朋友三個大場景,細節的業務邏輯差異較多,目前多業務代碼耦合在一起,增加新功能時需要考慮其他業務方,容易引入問題,開發和測試效率低 
- 內流容器和外流容器,形態相似但是代碼分離,主體代碼重復,新增功能時需要在兩個類中做重復開發,如:視頻預加載優化等,開發和維護成本高 
- 核心功能的監控和代碼防劣化的體系不完善 
Feed 容器多場景下承載業務
Feed 容器承載了基礎功能、直播、登錄、登出、性能監控、預加載等多個功能。
由于之前沒有做好管控,導致容器中業務相互耦合嚴重,業務邊界不清晰,開發過程中稍有不慎,就會對其他業務造成影響。
而且隨著業務迭代,逐漸呈現劣化趨勢,尤其是對于新業務接入,面對負責的代碼無從下手。
業務迭代效率低
由于代碼都在容器類中直接修改,一個版本經常會有多個業務在容器中進行修改導致沖突的情況,此時就需要多方進行 review,保證改動不出問題,往往還要平臺業務的同學進行支持,業務的整體迭代效率比較低。
防劣化&監控缺失
業務耦合,對代碼改動沒有監控,導致 FeedViewController 越來越膨脹。因為沒有合理架構導致無法做拆分,代碼劣化越來越嚴重,而且基于現狀無法進行防劣化。
目標方案
為了解決上述問題,首先設定好目標,然后根據目標提出解決方案,最終落地實現,驗證目標是否達成。
目標
- 架構分層,明確每層職責,容器和業務解耦,多業務之間解耦,做到容器和業務各自閉環; 
- 業務組件可插拔,不同場景支持靈活的組合和擴展業務組件; 
- 搭建監控體系,實現穩定性、性能、問題定位,建立看板,實時了解各項指標; 
- 防劣化,容器和業務分倉隔離,收斂維護人員; 
思路
根據上述的目標,從下面四點進行思考和設計:
- 明確業務開發痛點,多業務合作開發效率低、設計不合理模塊使用成本高等; 
- 自上而下設計,保證整體業務架構設計的合理性,明確優化方向; 
- 分層開發和上線驗證,降低上線風險和全量成本; 
- 架構防劣化,收益可衡量; 
方案
針對 Feed 容器內部多場景、多業務耦合導致整體維護困難,新業務接入成本高的問題,首先按照場景、業務和功能維護進行拆分梳理。在拆分完成后為了方便各個業務進行維護,設計了 ControlerKit 工具實現了生命周期方法的分發,并且通過 Context 進行狀態管理,實現了各個業務間的通信和狀態維護。
整體架構
基礎容器
Feed 基礎容器,采用組件化框架,支持基礎組件和業務組件的動態組合和擴展,由業務無關、統一的列表形態組成,通過數據驅動頁面展現。同時對外暴露生命周期事件,方便組件進行監聽。其中基礎容器由平臺方進行統一維護,并提供了完善的監控體系,方便進行問題的定位和追查。
基礎組件
Feed 容器的基礎組件部分,采用的方式是平臺方統一進行維護。目前的基礎組件,主要包括播放控制、播放策略優化、列表預加載以及頁面管理等。
其中,全屏 Feed 相關的基礎組件,為多業務共用,具備可復用、可擴展等優勢。
業務組件
業務組件是和業務強相關的組件,業務方可以根據自身的需要進行靈活定制,組件本身可插拔,由各業務方進行維護。
應用場景
業務方基于 Feed 容器,組合業務組件和基礎組件構建的頁面,在構造過程中可以基于配置文件實現容器的定制,比如推薦和關注。
容器化工具
多個業務耦合在同一個容器中,導致容器類越來越臃腫,一方面造成各方同時維護越來越困難,另一方面對于新業務和新同學接入十分不友好,需要花費很多時間熟悉上下文以避免改動對其他業務造成影響。
為此設計了 ControllerKit 庫,該庫實現了復雜頁面的分發,解決 ViewController 臃腫問題,規范代碼拆分標準,提供分發方法的能力。各個接入方按照規則注冊后,實現自己關心的生命周期方法,并在方法中實現對應的邏輯即可。
ContainerViewController
ContainerViewController 是容器 ViewController,實現了 ContainerProtocol,保存了上下文環境,負責了各個生命周期方法的分發。
ContainerProtocol
聲明了容器對外提供的屬性和方法,方便各個 SubController 進行訪問。
ControllerProtocol
聲明了基礎的聲明周期和共有的方法。
Controller
Controller 是將 ViewController 中的代碼拆分出來的子模塊,可以接收分發出來的 viewDidLoad、viewWillAppear 等生命周期及自定義方法調用,還可以向 ViewController 中添加子 View。
ControllerManager
ControllerManager 負責 Controller 的注冊、管理、方法分發。通過 classNameArray 返回 Controller 的字符串類名數組即可,可以支持 Controller 在其他倉庫的能力
Manager 需要聲明分發的 Controller 協議,只需要聲明,不需要實現,Manager 內部會通過消息轉發機制統一分發。
各角色之間的關系
ContainerViewController 實現了 ContainerProtocol,并持有 ControllerManager,各個子 Controller 注冊到 ControllerManager 中,各個 Controller 可以通過 ContainerProtocol 訪問容器的能力,ControllerManager 通過 ControllerProtocol 里面聲明的方法進行分發。
比如:ContainerViewController 初始化后調用 viewDidLoad 時,會通過 ControllerManager 依次分發到實現該方法的 controller 中,各個 Controller 在自己的 viewDidLoad 方法中實現自己的邏輯即可。
Controller 優先級
- 方法分發優先級按照數組提供的順序,因此更基礎的 Controller 應排在前面 
- 優先級由注冊順序決定,因此不同方法優先級無法調整,也不希望有調整,無法滿足時,通過其他方式實現 
Feed 容器的實現
根據 ControllerKit 對 Feed 容器的類結構改造如下所示
- FeedViewController 作為容器,實現容器能力,對外通過 FeedContainerProtocol 被訪問 
- Controller 對應業務組件 
- FeedControllerManager 負責組件的注冊、管理和事件的分發 
基于 ControllerKit 的設計和實現
各個類和協議的介紹:
- FeedContainerProtocol - 容器層通過 FeedContainerProtocol 對外提供能力 
- 避免業務方直接訪問和修改容器類 
- 該協議提供了業務層需要的各種能力和接口 
- 由平臺方進行維護 
 
- FeedControllerProtocol - 業務層協議通過 FeedControllerProtocol 聲明 
- 定義了各個生命周期相關的方法,被各個業務 controller 實現 
- 各個實現業務只需要在對應的生命周期方法中增加自身的邏輯即可 
- 被注入的 controller 會在相應的時機被調用到 
- 業務自閉環 
 
- Context 與 ContainerProtocol 的定位和區別 - FeedContainerProtocol 用來給 controller 提供 FeedViewController 實現的能力 
- FeedContext 中存放 Controller 共用的狀態 
- 兩個都能實現通信,但 context 更偏重于狀態,而 ContainerProtocol 更偏重于能力,比如頁面滾動、數據刷新 
 
- 業務組件定義 - 定義業務 Controller 類 
- 實現 FeedControllerProtocol 協議 
- 在對應的生命周期方法中實現對應的業務邏輯 
- 若 FeedControllerProtocol 不滿足情況時根據之前說明方式在協議中增加新的生命周期方法,同時同步增加到 FeedContainerProtocol ,以便分發 
 
重構后業務迭代方式
- 框架由平臺業務架構方維護 
- 其他業務的框架擴展需要提交到架構方,由架構方開發 
- 其他業務提交的方案和修改,交由架構方 review 
- 業務方的代碼,業務方自閉環 
防劣化建設
為了防止隨著業務的迭代,Feed 容器逐漸劣化,需要進行防劣化建設。首先進行框架和業務分倉:
- 代碼隔離,修改權限收斂; 
- 框架部分,線下做 Pipeline 準入,Lint 檢查是否符合容器規則; 業務方修改容器代碼,review 通過后才能合入 
新方案優勢
- 業務解耦,明確了業務和容器的職責,邊界清晰 
- 降低 FeedViewController 維護成本 
- 減少新業務接入成本 
- 方便做防劣化 
接入示例
以下以興趣選擇和業務為例,介紹新老業務的接入。
新功能接入 - 興趣選擇
興趣選擇是新的類型的卡片,需要進行卡片注冊并處理相關邏輯。
歷史方案
FeedViewController 直接進行修改,包括如下內容:
- 增加狀態管理屬性 
- 需要在 tableview delegate 和 scroll 滾動等多個方法中增加相應的處理邏輯 
- 處理注冊卡片邏輯 
新方案
- 抽取單獨的業務 Controller - 在生命周期方法中處理興趣選擇相關邏輯 
- 業務相關的屬性在 Controller 中聲明和維護 
 
- Controller 注冊到 ControllerManager 
- 在對應的 Controller 中進行自己的業務處理即可,不需要了解容器本身的其他業務邏輯 
存量功能拆分 - Feed 監控
Feed 監控功能在 FeedTableVC 中處理了很多業務,而且這些邏輯也其他業務存在著耦合。
- 網絡請求監控和數據處理 
- 頁面滾動 
- 播放處理 
- ... 
采用新方案進行拆分
首先創建 FeedMonitorController,增加業務相關的屬性、生命周期方法中實現對應的邏輯,之后抽取單獨的業務 controller 在生命周期方法中處理熟人相關邏輯。同時注冊到 controllerManager 中,并設置 AB、原有代碼判斷 AB。上線驗證,全量后刪除容器老代碼。之后業務自閉環,再進行迭代時直接在 FeedMonitorControlle r 內容修改即可。
當前進展&后續規劃
規劃和節奏
| 梳理現狀; 重構方案設計和評審; | 新增功能基于新組件開發; 存量業務組件拆分和驗證 | 業務接口合理化:Feed 容器對外暴露能力,業務調用; 基礎能力接口合理化:Feed 容器暴露給組件的能力,播控、數據操作等 | 組件化框架橫向應用,詳情頁 Feed 等使用新架構 | 
重構后的收益
- 業務解耦后,容器本身穩定,業務方各自維護自身業務,提高了整體的穩定性 
| 因為業務耦合,需要了解 Feed 的結構和多業務的細節,新同學熟悉的時間需要 2 天左右;在實現過程中,由于多個業務同時進行迭代,相互影響,質量無法保障 | 只需要在自己的業務 Controller 開發即可,無需關心容器的結構以及其他業務方,極大的提高了開發和迭代效率;改動不影響其他業務線的代碼,保障了代碼的穩定性 | 
- 全量業務在業務組件中實現了自閉環 - 版本進行了映射 
| 1.7 - 2.0 | 39 | 19 | 32.8% | 
| 1.3 - 1.6 | 31 | 18 | 46.15% | 
| 0.9 - 1.2 | 25 | 13 | 34.21% | 
| 0.5 - 0.8 | 16 | 23 | 58.9% | 
| 0.1 - 0.4 | 12 | 19 | 61.2% | 
By 王展、張宇、羅群鋒、谷春暉
加入我們
我們是抖音主框架研發團隊,負責抖音&抖音極速版核心場景的開發迭代,首頁推薦是抖音最大的流量入口,有極大的成長發展空間。團隊同時還支持多種內容服務,短視頻、中視頻、合集、熱點等,致力于在各種場景均能給用戶提供極致的消費體驗。我們還有獨立團隊在持續地擴寬設備邊界,實現多終端覆蓋,在智能音箱、車機甚至冰箱上你都能看到抖音。點擊原文鏈接期待優秀同學加入我們,一起“激發創造,豐富生活”。
?點擊“閱讀原文”,加入我們吧!
總結
以上是生活随笔為你收集整理的抖音 iOS 推荐 Feed 容器化总结的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 二次元日系游戏制作工具 - live2d
- 下一篇: JAVA调用R语言 (未完)
