1. 程式人生 > >DynamicCocoa:滴滴 iOS 動態化方案的誕生與起航

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

我和本文的作者孫源早就認識,我參加過孫源組織的好幾次線下分享活動。孫源是一個對技術喜歡刨根問底的人,熱愛分享和開源,同時特別喜歡狗,除了他的頭像外,他在百度時的開源組織都叫 forkingdog。

孫源後來去了滴滴 App 架構組,近期一直在潛心研究編譯器相關的東西,如果你關注過他今年在 MDCC 和 iDev 大會上的分享的話,你就會發現他的分享內容都與編譯器有關。其實,他分享這些是有原因的,因為他們團隊在做一個很牛逼的動態化方案,可以直接把 Objective-C 程式碼經過編譯,轉換成 JS 程式碼後下發給客戶端。

我一直不知道這裡面的技術細節,直到上週末他找到我,希望在這裡發表他們的 iOS 動態化方案,於是我就有幸成為了本文的最早一批讀者。

讀完之後,我意識到,他們團隊的這篇文章將會極大促進業界內對於 iOS 動態化方案的探索,也將會對其它動態化方案,例如 React Native, Weex, LuaView, 甚至 JSPatch 造成深遠影響。

這篇文章你一定得看,感謝滴滴 App 架構組授權發表。


方案誕生

動態化一直是 App 開發夢寐以求的能力,而在 iOS 環境下,Apple 禁止了在 Main Bundle 外載入和執行的自己的動態庫,所以像 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”


語法支援

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 也是由對應的兩部分構成:

  • 在 Clang 的基礎上,實現了一個 OC 原始碼到 JS 程式碼的轉換器

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

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


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


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

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

另一部分是要整合進 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,都可以無差別的呼叫。

  • 雖然 runtime 提供了動態建立 OC Class 的 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 開發的乾貨技術文章分享給大家:

相關推薦

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

我和本文的作者孫源早就認識,我參加過孫源組織的好幾次線下分享活動。孫源是一個對技術喜歡刨根問底的人,熱愛分享和開源,同時特別喜歡狗,除了他的頭像外,他在百度時的開源組織都叫 forkingdog。 孫源後來去了滴滴 App 架構組,近期一直在潛心研究編譯器相關的東西,如

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

作者簡介: 孫源(@我就叫Sunny怎麼了),滴滴出行 iOS 技術專家,多年專注於 iOS 開發,現就職於滴滴 App 架構組,在技術上做探索和深挖;善於刨根問底,對未知的東西興趣強烈,程式碼風格強迫症;同時喜歡寫部落格(http://blog.sunn

Netty2粘包/拆包問題使用LineBasedFrameDecoder的解決方案

substr string 技術分享 query coder 消息頭 handle decode sync 什麽是粘包、拆包 粘包、拆包是Socket編程中最常遇見的一個問題,本文來研究一下Netty是如何解決粘包、拆包的,首先我們從什麽是粘包、拆包開始說起: TCP是個

iOS開發簡記(1)指定APP的圖標啟動圖

appstore 技術分享 tps http data 對象 暫時 -c 系統 各位兄弟姐妹們,早上好,本人花了將近一個月的時間打造了一個完整的IOS版的App, 期間包括開發,測試,上線審核,現在花點時間把實現的過程分享給大家,“知音”app功能簡單,適合對象為初學者,後

iOS應用架構談(3)網路層設計方案

前言 網路層在一個App中也是一個不可缺少的部分,工程師們在網路層能夠發揮的空間也比較大。另外,蘋果對網路請求部分已經做了很好的封裝,業界的AFNetworking也被廣泛使用。其它的ASIHttpRequest,MKNetworkKit啥的其實也都還不錯,但前者已經棄坑,後者

IOS開發~UISCrollViewUITableView巢狀使用終極解決方案

問題由來:專案需要做類似網易新聞的那種UIScrollView上放多個UITableView的效果,其中UITableView還要有下拉重新整理效果。 一開始的思路,也是最直觀的思路就是一個UIScrollView上放多個UITableView,然後發現UITab

802.11協議精讀19Wi-Fi的起源,誕生發展

序言 本來這一節打算開始討論802.11e的內容,因為從無線區域網誕生到成型,尤其是演進的過程中,一個重要的起步點就是802.11e,很多在802.11n,802.11ac,802.11ah之中所引入的內容,實際上在802.11e中都可以發現其的引導,更一般而

搞懂分散式技術11分散式session解決方案一致性hash

session一致性架構設計實踐 原創: 58沈劍 架構師之路 2017-05-18 一、緣起 什麼是session? 伺服器為每個使用者建立一個會話,儲存使用者的相關資訊,以便多次請求能夠定位到同一個上下文。 Web開發中,web-server可以自動為同

Xcode 內存泄露檢查出現nil returned from a method that is expected to return a non-null value iOS 解決方案

idt d+ lai 泄露 分享 ssi compiler 解決 hat   在 使用 Xcode 檢查內存泄露時(cmd+shift+B)運行,出現了一個警告:nil returned from a method that is expected to return a

iOS 動態更新方案 JSPatch React Native 的對比

JSPatch 是 iOS 平臺上的一個開源庫,只需接入極小的三個引擎檔案,即可以用 JavaScript 呼叫和替換任意 Objective-C 方法,也就是說可以在 App 上線後通過下發 JavaScript 指令碼,實時修改任意 Objective-C 方法的實

《開源框架那點事兒19》特斯拉建“樁”開源的生命力

充電站 otto 期權 eas gb2 平臺 程序 蘋果 名詞 在美國的矽谷,在中國的中關村,很多的技術先驅者懷揣夢想。用激情不懈地追求著他們宏遠的目標和巨大的個人財富。開源。正在成為這個時代眼下最火熱的名詞之中的一個。開源精神,作為一種合作協同的驅動力,正在發展為一種更

Qt入門之基礎篇 ( 一 ) Qt4及Qt5的下載安裝

mingw ins 第3版 點擊 調試 但我 關系 構建 eas 轉載請註明出處:CN_Simo. 導語: Qt是一個跨平臺的C++圖形界面應用程序框架。它提供給開發者建立圖形用戶界面所需的功能,廣泛用於開發GUI程序,也可用於開發非GUI程序。Qt很容易擴展,並

Python-10Python語法基礎-運算符表達式

輸出 false col 先後 邏輯與 尋址 分享 hello 邏輯或 1、Python運算符簡介   1)什麽是運算符     在Python中經常需要對一個或多個數字進行操作,2+3中的+是運算符,"hello"*20中的*也是運算符   2)運算符有哪些     + 

iOS開發中地圖定位

視圖 編寫 aps 簡單 -a 第三方 span spa margin   不管是QQ還是微信的移動client都少不了定位功能,之前在微信demo中沒有加入定位功能,今天就寫個定位的小demo來了解一下定位和地圖的東西。地圖和定位看上去是挺高大上一東西。其有使用方法比

企業Shell面試題14開發腳本入侵檢測報警案例

開發腳本入侵檢測與報警案例、md5sum指紋、 面試及實戰考試題:監控web站點目錄(/var/html/www)下所有文件是否被惡意篡改(文件內容被改了),如果有就打印改動的文件名(發郵件),定時任務每3分鐘執行一次。1.1問題分析1)首先要說明的是,思考過程的積累比實際代碼開發的能力積累更重

《暢銷的原理》4星。關於判斷決策的研究的綜述。

使用 src 分享 積極 對比 大量 性能 相同 效應 全書是關於人類的判斷與決策的心理學研究的綜述。有一些內容跟《屏幕上的聰明決策》重合。總體評價4星,還不錯,首先是因為是研究的綜述,每一個觀點都有出處,相對比較靠譜,其次有不少觀點還是比較新穎的。 以下是書中一些信

iOS開發--本地通知遠程通知

授權 atom nbsp sel bject 面試 tar 生效 nat iOS開發--本地通知與遠程通知 作者 雷潮 關註 2016.02.01 00:18* 字數 1921 閱讀 8898評論 1喜歡 41 這裏是指推送通知跟NSNotification有區別

iOS--Runtime之一--類對象

runtime runt col 記錄 color -- line isa blog 一、 1.暫記錄 + (Class)class { return self; } - (Class)class { return object_getClass(s

JAVAEE學習——struts2_03OGNL表達式、OGNLStruts2的結合和練習:客戶列表

數據 setvalue mage 工作 準備 nor fig 存在 dir 一、OGNL表達式   1.簡介   OGNL:對象視圖導航語言. ${user.addr.name} 這種寫法就叫對象視圖導航。  OGNL不僅僅可以視圖導航.支持比EL表達式更加豐富的功能

IOS開發之——objectForKeyvalueForKey在NSDictionary中的差異

什麽 iat app 報錯信息 lease 方法 去掉 defined atom 從 NSDictionary 取值的時候有兩個方法,objectForKey: 和 valueForKey:,這兩個方法具體有什麽不同呢? 先從 NSDictionary 文檔中來看這兩個方法