DynamicCocoa:滴滴 iOS 動態化方案的誕生與起航

DynamicCocoa:滴滴 iOS 動態化方案的誕生與起航

 

這是滴滴 App 架構組發佈的第一篇公共技術文章,本文將介紹自研的 iOS 動態化方案 DynamicCocoa。App 架構組做爲技術航海者,不只要爲滴滴客戶端作技術的儲備,也承擔着向你們分享技術的職責,DDApp 這個公衆號從此還會推送其餘 iOS、Android 開發乾貨,敬請關注~前端

 

 

方案誕生git

 


動態化一直是 App 開發求之不得的能力,而在 iOS 環境下,Apple 禁止了在 Main Bundle 外加載和執行的本身的動態庫,因此像 Android 同樣下發原生代碼的方案被堵死。後端

 

後來像 ReactNative、Weex 這樣的基於 Web 標準的跨端方案出現,各大公司都有對其進行嘗試,但對於滴滴現狀,也許並不適合:xcode

  • 滴滴 App 強交互、以地圖爲主體、端特異性高緩存

  • 客戶端人員充足,跨技術棧學習和開發有較大成本ruby

  • 大量固化 Native 代碼,重寫成本高微信

 

因此咱們思考,能不能作一套保持 iOS 原生技術棧、不重寫代碼就神奇的擁有動態化能力的方案呢?網絡

 

因而,咱們設計和實現了一個具備里程碑意義的 iOS 專屬動態化方案:DynamicCocoa多線程

 

DynamicCocoa 初識架構

 


DynamicCocoa 可讓現有的 Objective-C 代碼轉換生成中間代碼(JS),下發後動態執行,相比其餘動態化方案,優點在於:

  • 使用原生技術棧:使用者徹底不用接觸到 JS 或任何中間代碼,保持原生的 Objective-C 開發、調試方式不變

  • 無需重寫已有代碼:已有 native 模塊能很方便的變成動態化插件

  • 語法支持完備性高:支持絕大多很多天常開發中用到的語法,不用擔憂這不支持那不支持

  • 支持 HotPatch改完 bug 後直接從源碼打出 patch,一站式解決動態化和熱修復需求

     

 

不管是動態化仍是 HotPatch,咱們都能讓開發者:"Write Cocoa, Run Dynamically"

 


 

語法支持

 


DynamicCocoa 能支持絕大部分平常使用的 Objective-C / C 語法,挑幾個特殊的好比:

  • 完整的 Class 定義:interface、category、class extension、method、property,最重要的是支持完備的 ivar 定義,保持和 native 徹底一致的實例內存結構

  • ARC:能夠正確處理 strong、weak、unsafe_unretained 等對象的引用計數,對象的 ivar 也能夠正確的釋放

  • C 函數:支持 C 函數的定義與 C 函數的調用、內聯函數的調用

  • 可變參數:支持 C 與 OC 的可變參數方法的調用,如 NSLog

  • struct:支持任意結構體的使用,無需額外處理

  • block:支持建立和調用任意參數類型的 block

  • 其餘 OC 特性:如 @selector、@protocol、@encode、for..in 等

  • 其餘 C 特性:支持使用宏、static 變量、全局變量,取地址等

 

舉個栗子,你能夠放心的使用下面的寫法,並能被正確的動態執行:

資源支持

 


一個功能模塊,除了代碼外,資源也是必不可少的,DynamicCocoa 的動態 bundle 支持:

  • xib 和 storyboard

  • xcassets

  • 不放在 xcassets 裏的圖片資源

  • 其餘資源文件

 

對於習慣於使用 IB 來開發 UI 的人來講,這將是一個很好的開發體驗。

 

 

工具鏈支持

 


咱們使用 ruby 開發了一套命令行工具( 類比爲 xcodebuild ),大幅簡化了配置開發環境、OC 代碼轉換、資源處理、打包的複雜度,它能夠:

  • 解析 Xcode Project:讀取工程編譯選項,保持和 native 編譯參數一致

  • 增量編譯:緩存 JS 轉換結果,只從新轉換修改過的文件,大幅提升 build 速度

  • 連接:分析類依賴,將多個 JS 按依賴順序合併,提升文件讀取速度

  • 資源編譯:編譯用到的 xib、storyboard 和 xcassets

  • 打包:將 JS、資源等打包成 bundle

 

對於開發者來講,就像 pod 命令同樣,全部操做均可以經過這個命令完成。

 

動態插件開發流程

 


首先 App 中須要集成 DynamicCocoa Engine SDK,用來執行下發的 bundle

開發到發佈的流程以下圖所示:

固然,DynamicCocoa 只提供命令行工具和 Engine SDK,能夠完成本地打包、運行和測試,而線上發佈後臺、服務端、CDN 等須要自行解決。

 

在滴滴內部,咱們構建了開發、Review、線上迴歸測試、灰度、發佈、回滾、統計的閉環系統,以服務的形式給內部接入。

 

HotPatch 過程

 


HotPatch 本質上是方法粒度上的動態化,因此在整個框架搭建起來後,HotPatch 也不難實現,使用 DynamicCocoa 作熱修復的最大優點是開發者依然只對源碼負責,修改完 bug 後,打個 patch 包,修復成功後把源碼改動直接 push 到代碼倉庫就好了。

 

假設咱們發現了下面的 bug:


而後在 native 進行修復並自測:

自測完成後,在這個方法後面添加一個神奇的 Annotation


使用命令行工具在 patch 模式下進行打包,就能把全部標記了的 method 提取出來,分別轉換成 JS 表示,打到一塊兒進行發佈。

 

除了修改一個方法外,patch 模式還支持:

  • 調用原方法

  • 新增一個方法

  • 新增一個 property 來輔助修復 bug

  • 新增一個 Class

 

最後,開發者能夠安心的把修改後的代碼(甚至能夠保留 Annotation)git push,完成熱修復工做。

 

打開黑箱

 


 

就像 Objective-C 是由 Clang 編譯器和 Objective-C Runtime 共同實現同樣,DynamicCocoa 也是由對應的兩部分構成:

 

  1. 在 Clang 的基礎上,實現了一個 OC 源碼到 JS 代碼的轉換器

  2. 實現 OC-JS 互調引擎的 DynamicCocoa SDK

 

咱們知道,Clang-LLVM 的標準編譯流程是從源代碼通過預處理、詞法解析、語法解析生成語法樹,CodeGen 生成 LLVM-IR,進入編譯器後端進行優化和彙編,最終生成目標文件 (Mach-O)

而咱們既但願 Clang 幫助完成源碼處理的步驟,又但願生成結果是 JS 表示形式,因而在 Clang 生成抽象語法樹(AST)後,咱們進行接管,實現了一個 OC2JS CodeGen,遍歷各個特定語法節點輸出 JS 表示:

因爲轉換器和 Clang 前端標準編譯流程相同,因此只要 native 代碼能 build,轉換器就能 build,這也是 DynamicCocoa 能讓動態包和 native 保持嚴格一致的先決條件。

 

另外一部分是要集成進 App 的 DynamicCocoa SDK,它的職責是爲 JS 中間代碼提供 Runtime 環境,實現 OC-JS 的互調引擎,可以加載動態 bundle,提供便捷的 API,總體架構以下:

一些有趣的點:

 

  • 底層使用 libffi 來處理各個架構下的 calling conventions,實現 caller 調用棧的構建和 callee 調用棧的解析,用於實現 OC / C 函數調用、動態 imp、block 等。

  • 因爲 JS 的弱類型,數值變量在作計算時很容易丟失類型信息,好比 int a = 1 / 2; 在 OC 中表示整除,結果爲 0,但進入 JS 就都會按照 double 計算,結果爲 0.5,形成了不一致。因此 DynamicCocoa 接管了 JS 中的類型信息,強轉或運算符都須要特殊處理。

  • 爲了實現 block,咱們構造了和 native block 一致的內存結構,不管是 JS 建立的 block 仍是 native 傳進 JS 的 block,均可以無差異的調用。

  • OC Class 的動態建立在 runtime 中提供了 API,但只能建立 MRC 的 Class,致使 ARC 下 ivar 並不會乖乖釋放,咱們深刻到 Class 和實例真實內存結構中,給動態建立的類增長了 ARC 能力,並按照 Non-Fragile ABI 模擬真實 ivar 內存佈局和 ivar layout 編碼,若是你重寫了 dealloc 方法,DynamicCocoa 甚至可以像 native 同樣自動調用 super。

     

     

     

 

DynamicCocoa 帶來的改變

 


DynamicCocoa 動態化技術給 App 開發帶來了很大的想象空間:

  • 低成本的動態化:無需額外學習,無需重寫代碼,能夠快速的將已有模塊動態化

  • 協做方式:對於大團隊,發佈版本沒必要再彼此牽制

  • 功能快速迭代:無需通過審覈和 App Store 發版,像 h5 同樣隨發隨上

  • App 瘦身:native 只須要留好插件入口,實現由網絡下發,減小 App 體積

  • AB Test:沒必要侷限於 native 埋進去的 AB 功能 Test,發版後能動態下發各類 Test

相比跨端方案,也帶來了一個新思路:iOS 和 Android 都保留 native 開發模式,用各自的方式將 native 代碼直接動態化,保持各平臺的差別性。

Q&A

 


與 JSPatch 有什麼區別?

二者思路上都是實現 JS 和 OC 的互調:DynamicCocoa 的重點是動態化能力,優點在於徹底不用寫 JS 和更多的語法特性支持;對於 HotPatch 來講 JSPatch 是更加小巧、輕量的解決方案。

 

這套框架在滴滴 App 有上線使用麼?

有,在滴滴 App 已經上線並使用了好幾個版本,如滴滴小巴、專車接送機都有過 10k 級別的動態化模塊上線。

 

動態包運行的性能是否有很大降低?

動態 JS 代碼的運行要通過頻繁的 JSCore 和 OC 間的切換,性能相比 native 一定會有損耗,但通過優化,如今已經達到了無感知的程度:在咱們的實際使用中,若不在頁面上添加特定標誌,開發者和 QA 都沒法分辨出當前頁面運行的是 native 仍是動態包... 後續會有詳細的性能分析和你們分享。

 

動態包大小如何?

與資源大小和 native 源碼量有很大關係,不考慮資源的狀況下,量級大概在 10000 行代碼 100kb 的動態包。

 

是否支持多線程?

如今簡單的支持 GCD 來處理多線程,可使用 dispatch_async 將一個 block 放到另外一個 queue 中執行。

 

如何定位動態包的 crash?

動態 JS 代碼運行在 JSCore 中,並無直接獲取調用棧的方式,咱們提供了 stack trace 功能,將最近調用棧中每一個 JS 到 OC / C 的互調都記錄下來,在發生 crash 時即可以取出來做爲附加信息隨 crash 日誌上報給統計平臺,方便問題的定位。

 

會不會過不了蘋果審覈?

市面上不少動態化、HotPatch 方案都基於 JS 的下發,運行在原生 JSCore 上,相信只要不在審覈期間下發動態功能,Apple 是不太會拒絕的。

 

有沒有可能支持 Swift 直接動態化?

相比 OC,Swift 的動態化和 HotPatch 更加有難度,但咱們已經有了可行的方案,是能夠作到的,只是對於當前滴滴的現狀(絕大多數都在用 OC 開發),緊急程度並不高,後面再考慮支持。

 

是否有開源計劃?

有,咱們正在積極的準備相關事項,於 2017 年初考慮開源。

 

該從哪裏關注後續進展?

請關注滴滴 App 開發技術微信公衆號 DDApp,咱們會在上面發佈 DynamicCocoa 的最新的進展,也將會把滴滴 iOS 和 Android 開發的乾貨技術文章分享給你們:

相關文章
相關標籤/搜索