抖音 iOS 工程架構演進

前言介紹

2016.09.26,抖音版本 1.0.0 上線,隨後不斷迭代優化和豐富產品,截止目前,抖音日活躍用戶突破 6 億,短短 4 年間,抖音從零爆發性增加。shell

快速的業務發展也對技術支撐提出了更高的要求,爲了保障敏捷的業務開發,提高跨團隊的協同合做效率,提升本地研發和 CI/CD 效率,抖音 iOS App 工程架構在不一樣的階段進行了不一樣的技術方案的改進,知足合理的架構演化,同時又不影響正常的業務迭代速度。swift

抖音工程架構演進

架構演進的本質是爲了提升研發效率,提升代碼穩定性和保證代碼質量。架構要解決的問題是如何組織代碼。後端

合理的架構設計能夠解決大型項目跨團隊協做分工和多業務線並行開發的效率問題。抖音工程代碼從一開始就採用了組件化思路,依賴管理工具是定製版的 Cocoapods。緩存

如下動畫介紹了抖音工程架構經歷的四個階段的演進過程:markdown

623eb9ec-8827-44ff-a6a9-544edda4c2f9.gif 圖1:抖音項目工程架構演進架構

組件化

在大型項目快速發展的過程當中,要保證敏捷開發迭代的最大障礙就是快速膨脹的代碼體積致使的編譯效率問題,依賴關係複雜化問題,以及業務線代碼衝突問題。框架

移動端項目能夠類比後端項目中採用的微服務架構,要解決多業務線並行開發、並行測試問題,採用流水線式迭代開發,提升發版、集成、交付、提審、發佈效率,結合分治思想技術選型上能夠採用組件化的方案。less

大部分小型項目,組件化僅僅作到代碼分倉,使用 Cocoapods 的來管理組件依賴,就像抖音項目最初的工程形態。分佈式

可是對於幾百號人、幾十個業務線規模的大型項目,須要設計一套合理的組件分層架構,理清組件間依賴關係,須要 CI/CD 工具鏈支撐組件發版與集成,須要本地研發工具支撐本地代碼同步、工程配置、依賴管理和效率優化。ide

流水線式迭代開發

流水線(pipeline)技術是指在程序執行時多條指令重疊進行操做的一種準並行實現技術,該技術能夠充分提升資源的利用率,同時縮短產品的研發週期。 對於客戶端項目,流水線技術能很大程度知足敏捷開發迭代的節奏。

圖2:抖音流水線式迭代發版 圖2:抖音流水線式迭代發版

抖音工程架構演進

階段一:抖音原始工程架構(Original architecture of project)

圖3:抖音項目原始工程架構圖 圖3:抖音項目原始工程架構圖

抖音項目一開始是單體架構+Cocoapods,業務代碼、工程配置、資源文件所有放在一個大業務倉庫。由 Podfile 文件描述第三方倉庫的依賴版本。

圖4:抖音項目原始工程架目錄結構 圖4:抖音項目原始工程架目錄結構

階段二:分離殼工程後的工程架構(After splitting of host shell pod)

圖5:拆分殼工程後的工程架構 圖5:拆分殼工程後的工程架構

分離殼工程後,工程配置、部分系統資源、工程主入口被拆分到主宿主殼工程。

Podfile 拆分出版本依賴管理文件 Podfile.seer,由依賴管理平臺進行各個版本的容器化管理,業務倉跟隨宿主集成發版,打平依賴,解決版本依賴決議耗時問題。

大業務倉中的代碼和資源被拆分到各個業務線的倉庫下,由 podspec 文件描述內外依賴。業務線倉庫增長 ModuleInterface subspec,存放對外接口,採用依賴注入方式實現接口隔離,初步創建接口層。

業務倉庫之間規定只能依賴其餘業務倉庫的 ModuleInterface subspec,經過 lint 進行編譯檢查。

部分基礎能力代碼被拆分紅基礎倉庫,跟第三方倉庫同樣獨立發版。本地研發工具支持單倉開發和多倉開發,不參與代碼修改的倉庫經過二進制的方式進行連接。同時 CI 流程上也支持經過二進制打測試包,提升打包效率。

圖6:抖音項目拆分殼工程後目錄結構
圖6:抖音項目拆分殼工程後目錄結構

殼工程

圖7:殼工程抽象 圖7:殼工程抽象

爲了知足一個工程同時支持多個項目、部分業務線功能複用、部分業務線中臺化發展的需求,咱們把全部業務線抽象成獨立的 Pod,全部業務 Pod 必須經過宿主的殼工程進行集成發版。

殼工程包含了項目依賴的 Pod 信息描述,同時還包括工程的配置、部分系統級別的資源文件、工程主入口代碼。基於多份宿主殼工程,一份代碼能夠打包出抖音、抖音極速版等項目。

同時,基於宿主殼工程,一些業務線能夠經過自動化同步生成本身的子殼工程,實現業務線本身的 Example 工程,進行獨立開發,好比有語音通話的 Example 工程,有工具的 Example 工程,有直播的 Example 工程等等。

圖8:子殼工程配置同步同步 圖8:子殼工程配置同步同步

接口層

接口層顧名思義,只提供依賴的抽象接口,全部接口都是 protocol 協議聲明。

接口層限制了全部其餘依賴,類、枚舉、 外部協議都採用前向聲明,podspec 上只容許聲明對 DI(依賴注入)框架的依賴。接口層知足封裝、隔離和組合的原則。

  • 業務層面對外封裝了實現代碼;
  • 編譯層面隔離了組件間依賴傳遞,減小頭文件 import 嵌套提升編譯緩存的命中率,對於 swift 業務組件,還能達到減小編譯傳遞的問題;
  • 架構層面聲明抽象協議支持接口組合;
  • DI 容器框架同時支持 stateless DI 容器,也支持 stateful DI 容器。

依賴打平

  • 採用 Cocoapods 自己自帶的版本依賴決議進行版本分析會消耗大量的時間;

  • Podfile.lock 過於繁瑣,可讀性不好,難以解決 Podfile.lock 的衝突;

  • 隱式依賴被動/不符合預期地升級,難以肯定性地聲明全部依賴,防止隱式依賴被升級;

  • 依賴版本在 Podfile/Podfile.lock 重複聲明,增長了解決衝突的成本;

  • Podfile.lock 參與依賴版本決議流程比較複雜,會出現不符合預期的狀況。

圖9:把版本管理和倉庫源信息遷移到 Podfile.seer 文件
圖9:把版本管理和倉庫源信息遷移到 Podfile.seer 文件

  • hook 掉 Cocoapods 採用 podfile.lock 進行版本決議的邏輯,採用 Podfile.seer 文件直接描述全部組件的版本信息,打平依賴。

階段三:單倉多組件工程架構(Multicomponents in single repo)

圖10:拆分單倉多組件後的工程架構 圖10:拆分單倉多組件後的工程架構

採用單倉多組件後,每一個業務線倉庫支持添加 podspec 增長組件,實現更小粒度的二進制依賴。業務線倉庫內劃分業務實現層、業務接口層、服務層和基礎層,都是經過集成方式發版。

新增的服務層主要存放公共的業務邏輯和通用服務,限制 UI,一是知足業務邏輯複用,二是知足子殼工程最小化二進制依賴。同時服務層的服務接口也達到隔離依賴傳遞的目的,在不一樣的宿主上,支持經過改變服務層實現替換後臺能力或者底層能力。創建分層間的依賴准入規則,完善 lint 編譯連接檢查。

圖11:單倉多組件目錄結構 圖11:單倉多組件目錄結構

編譯連接完備性校驗

  • 編譯校驗:分開編譯各個 subspec,確保每一個 subspec 的依賴是正確的(因爲 subspec 沒有編譯隔離)
  • 接口符號校驗:校驗當前接口組件(ModuleInterface)中符號是否完備的,以保證其餘組件單獨引用是否能正常使用。如 extern 聲明的全局變量。

分層依賴准入規則:

  • 高層依賴低層
  • 實現依賴接口
  • 接口層無依賴
  • 前向聲明優先
  • 服務層去"UI"

如下動畫展現了業務實現層和服務實現容許依賴的分層:

2021412-164953.gif 圖12:組件依賴關係示意圖動畫

階段四:Example 子殼工程架構(Subshell for bizcomponent in example project)

圖13:子殼工程架構 圖13:子殼工程架構

每一個業務倉從宿主同步工程配置構建子殼工程。增長 AWELaunchKit 爲子殼工程提供運行時的基礎能力。經過服務層提供業務間運行時共享的服務能力,知足代碼複用和更小二進制依賴。

圖14:子殼工程目錄結構 圖14:子殼工程目錄結構

AWELaunchKit

AWELaunchKit 框架爲宿主和其餘子殼工程提供了基礎服務的依賴和初始化配置。同時提供了一套啓動加載的 BootTasks 管理框架,部分業務涉及啓動相關的邏輯能夠在業務倉對應的服務層中實現,並經過 BootTasks 管理框架註冊到啓動加載器裏面。

同時框架還提供了一套宿主 UI 入口和自定義入口框架。爲了方便測試和調試,也整合了整套測試調試框架。

圖15:子殼工程依賴關係 圖15:子殼工程依賴關係

組件化探索過程當中遇到的一些問題:

二進制污染

組件之間的依賴除了顯式的依賴,還存在不少隱式依賴,代碼層面,除了普通的接口依賴,還有宏依賴、枚舉依賴、全局變量依賴以及內聯函數等的依賴。單倉 lint 進行編譯連接完備性檢查並不能解決依賴變更對其餘二進制的影響。

所以須要藉助源碼層面的依賴分析,判斷當前組件的變動對其餘依賴當前組件的二進制是否有影響,在 CI 流程中及時發現並攔截。不然錯誤的二進制發版,會直接致使整個 CI 研發流程和本地研發都受到影響。

編譯優化

編譯優化最高效的方式就是提升緩存的利用率。對於本地研發和 CI 流程,都涉及分佈式編譯緩存同步。同時經過編譯參數優化、依賴優化、hmap 優化也能不一樣程度的提升編譯效率

主幹分支穩定性問題

對於多業務線並行開發,幾百號人的業務開發團隊,若是主幹分支一旦出現問題,那麼解決問題的時間就須要乘上幾百倍。所以,須要從編譯層面和運行層面都要有足夠的機制去保證一個穩定的主幹分支,才能保證業務側的長期穩定性。

業務層的依賴耦合問題

大型項目動則千萬行的代碼,代碼間的依賴關係是複雜的網狀關係。須要基於代碼的語法樹模型,從語義中去分析不合理的依賴,並輸出治理的方案。

咱們內部自研了源碼依賴關係分析平臺用於依賴關係分析監控和代碼治理,長期監控組件間的依賴度。同時,須要創建依賴健康度模型,從長期演進的角度去監控防止代碼的劣化。

圖16:spider 組件依賴分析平臺 圖16:spider 組件依賴分析平臺

總結

大型項目的組件化工做是一個系統性工程。涉及工程架構的改造、CI/CD 研發工具鏈的支撐、本地研發工具鏈的支撐,業務架構的設計優化,須要從各個方面綜合考慮成本和收益。

沒有最好的架構,只有更好的架構,在架構演進的過程當中,咱們須要充分考慮架構的改動對業務的影響以及能給業務帶來的收益。好的架構必定是能幫助業務節省時間,保證質量的。與此同時,咱們在架構改進的過程當中,要保證不能影響業務的正常迭代,因此向前兼容且避免大面積衝突也是很重要的事情。

組件化裏面到處都有驚喜,好比一個小小的 hmap 優化,能夠很大程度的減小編譯耗時,好比一個二進制的壓縮和解壓的優化,能夠很大程度減小 pod install 的總體耗時。

固然這裏面也會有不少很棘手的問題,須要經過一些特殊的方案解決,好比針對分佈式開發,因爲阻塞式發版必然會致使一些不一樣分支存在衝突的代碼發版後影響主幹的穩定性。

因爲文章篇幅有限,只能點到即止地介紹當前一些工做成果和思考,各個 Topic 還有一些新的方向在探索,若是你對 iOS 底層原理、架構設計、構建系統、自動化測試有深刻了解,快來加入咱們吧!

加入咱們

咱們是負責抖音客戶端基礎能力研發和新技術探索的團隊。咱們在工程/業務架構,研發工具,編譯系統等方向深耕,支撐業務快速迭代的同時,保證超大規模團隊的研發效能和工程質量。在性能/穩定性等方面不斷探索,努力爲全球數億用戶提供最極致的基礎體驗。

若是你對技術充滿熱情,歡迎加入抖音基礎技術團隊,讓咱們共建億級全球化 App。目前咱們在深圳、上海、北京、杭州、均有招聘需求,內推能夠聯繫郵箱:tech@bytedance.com,郵件標題: 姓名-工做年限-抖音-基礎技術-iOS/Android。或直接點擊連接查看部門所需崗位!


歡迎關注「字節跳動技術團隊」

投遞簡歷請聯繫郵箱:tech@bytedance.com

相關文章
相關標籤/搜索