你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 滴滴 iOS 動態化方案 DynamicCocoa 的誕生與起航

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

編輯:IOS開發基礎

作者簡介: 孫源(@我就叫Sunny怎麼了),滴滴出行 iOS 技術專家,多年專注於 iOS 開發,現就職於滴滴 App 架構組,在技術上做探索和深挖;善於刨根問底,對未知的東西興趣強烈,代碼風格強迫症;同時喜歡寫博客(http://blog.sunnyxx.com/),線上線下分享,貢獻開源(forkingdog)累計 star 破萬。

滴滴出行客戶端App架構團隊在對ReactNative、Weex進行調研嘗試後發現並不適用於滴滴現有業務,由此自研了iOS動態化方案——DynamicCocoa,在這篇文章中,作者詳細分享了它的背景以及具體功能實現。

方案誕生

動態化一直是App開發夢寐以求的能力,而在iOS環境下,Apple禁止了在MainBundle外加載和執行的自己的動態庫,所以像Android一樣下發原生代碼的方案被堵死。

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

  • 滴滴App強交互、以地圖為主體、端特異性高;

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

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

所以我們思考,能不能做一套保持iOS原生技術棧、不重寫代碼就神奇的擁有動態化能力的方案呢?

於是,我們設計和實現了一個具有裡程碑意義的iOS專屬動態化方案:DynamicCocoa

DynamicCocoa初識

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

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

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

  • 語法支持完備性高:支持絕大多數日常開發中用到的語法,不用擔心這不支持那不支持;

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

不論是動態化還是HotPatch,我們都能讓開發者“Write Cocoa,Run Dynamically”。

ff577fae869247068dd2853a6b65afdb_th.png

語法支持

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

  • 完整的Class定義:interface、category、classextension、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變量、全局變量,取地址等。

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

a67a4ced370444a0bba4a05f2fca2b3e_th.jpeg

資源支持

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

  • xib和storyboard;

  • xcassets;

  • 不放在xcassets裡的圖片資源;

  • 其他資源文件。

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

工具鏈支持

我們使用Ruby開發了一套命令行工具(類比為xcodebuild),大幅簡化了配置開發環境、OC代碼轉換、資源處理、打包的復雜度,它可以:

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

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

  • 鏈接:分析類依賴,將多個JS按依賴順序合並,提高文件讀取速度;

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

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

對於開發者來說,就像pod命令一樣,所有操作都可以通過這個命令完成。

動態插件開發流程

首先App中需要集成DynamicCocoaEngineSDK,用來執行下發的bundle開發到發布的流程如下圖所示:

fe86cf549e4647be83b2f4547622dd15_th.jpeg

當然,DynamicCocoa只提供命令行工具和EngineSDK,可以完成本地打包、運行和測試,而線上發布後台、服務端、CDN等需要自行解決。

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

HotPatch過程

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

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

706b4b9ace7e42e980b91de252b079f9_th.png

然後在Native進行修復並自測:

36.jpeg

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

37.jpeg

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

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

  • 調用原方法;

  • 新增一個方法;

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

  • 新增一個Class。

最後,開發者可以安心的把修改後的代碼(甚至可以保留Annotation)gitpush,完成熱修復工作。

打開黑箱

66.png

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

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

  • 實現OC-JS互調引擎的DynamicCocoaSDK。

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

14.png

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

15.png

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

注:轉換器是基於Clang開發的獨立命令行工具,它的使用並不會對原有的Xcode工程產生任何影響。

另一部分是要集成進App的DynamicCocoaSDK,它的職責是為JS中間代碼提供Runtime環境,實現OC-JS的互調引擎,能夠加載動態bundle,提供便捷的API,整體架構如下:

16.png

其中一些有趣的點:

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

  • 由於JS的弱類型,數值變量在做計算時很容易丟失類型信息,比如inta=1/2;在OC中表示整除,結果為0,但進入JS就都會按照double計算,結果為0.5,造成了不一致。所以DynamicCocoa接管了JS中的類型信息,強轉或運算符都需要特殊處理。

  • 為了實現block,我們構造了和nativeblock一致的內存結構,不論是JS創建的block還是native傳進JS的block,都可以無差別的調用。

  • 雖然runtime提供了動態創建OCClass的API,但只能創建MRC的Class,導致ARC下ivar並不會乖乖釋放,我們深入到Class和實例真實內存結構中,給動態創建的類增加了ARC能力,並按照Non-FragileABI模擬真實ivar內存布局和ivarlayout編碼,如果你重寫了dealloc方法,DynamicCocoa甚至能夠像native一樣自動調用super。

DynamicCocoa帶來的改變

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

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

  • 協作方式:對於大團隊,發布版本不必再彼此牽制;

  • 功能快速迭代:無需經過審核和AppStore發版,像HTML5一樣隨發隨上;

  • App瘦身:Native只需要留好插件入口,實現由網絡下發,減少App體積;

  • ABTest:不必局限於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中,並沒有直接獲取調用棧的方式,我們提供了stacktrace功能,將最近調用棧中每個JS到OC/C的互調都記錄下來,在發生Crash時便可以取出來作為附加信息隨Crash日志上報給統計平台,方便問題的定位。

會不會過不了蘋果審核?

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

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

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

是否有開源計劃?

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

該從哪裡關注後續進展?

滴滴App架構組正式創建了微信公眾號DDApp(直接搜索),這也是其中的第一篇文章,我們會在上面發布DynamicCocoa的最新的進展,還會把滴滴iOS和Android開發的干貨技術文章分享給大家,歡迎關注。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved