聊聊JSPatch的動態性原理

JSPatch簡介

JSPatch 是一個開源項目(Github鏈接),只需要在項目里引入極小的引擎文件,就可以使用 JavaScript 調用任何 Objective-C 的原生接口,替換任意 Objective-C 原生方法。目前主要用于下發 JS 腳本替換原生 Objective-C 代碼,實時修復線上 bug。已超過 3500 個 App 在使用,成為 App 標配功能。

基礎原理

1、Objective-C 方面
Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在于:代碼時更具靈活性,我們可以把消息轉發給我們想要的對象,或者隨意交換一個方法的實現等。這種特性意味著需要一個運行時系統來執行編譯的代碼,它讓所有的工作可以正常的運行。這個運行時系統即 Objc Runtime。Objc Runtime 其實是一個Runtime庫,它基本上是用C和匯編寫的,這個庫使得C語言有了面向對象的能力。
要理解 Runtime 庫,首先要了解 Objective-C 類與對象基礎數據結構。類是由 Class 類型來表示的,它實際上是一個指向 objc_class 結構體的指針,定義可在 objc/runtime.h 中看到:

objc.jpg

在這個定義中,這里只關注3個字段

1、ivars :存放屬性鏈表,記錄類實例的所有屬性定義。
2、methodLists :存放法樹鏈表,記錄類實例的所有方法實現指針。
3、cache:用于緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據 isa 指針去查找能夠響應這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是 methodLists 中遍歷一遍,性能勢必很差。這時 cache 就派上用場了。在我們每次調用過一個方法后,這個方法就會被緩存到 cache 列表中,下次調用的時候 Runtime 就會優先去 cache 中查找,如果 cache 沒有命中,才去 methodLists 中查找方法。這樣大大提高了調用的效率。

同時 objc/runtime.h 中還提供了大量的 API 來操作類與對象。類的操作方法大部分是以 class 為前綴的,而對象的操作方法大部分是以 objc 或 object_ 為前綴。這里我們只關注方法操作函數,如下:

class.jpg

1、class_addMethod:如果本類中包含一個同名的實現,則函數會返回 NO。如果要修改已存在實現,可以使用 method_setImplementation。
2、class_replaceMethod:該函數的行為可以分為兩種:如果類中不存在 name 指定的方法,則類似于 class_addMethod 函數一樣會添加方法;如果類中已存在 name 指定的方法,則類似于 method_setImplementation 一樣替代原方法的實現。
3、method_setImplementation:重置方法實現。
4、method_exchangeImplementations:交換方法實現。

在 Objective-C 中調用一個方法,其實是向一個對象發送消息,查找消息的唯一依據是 SEL 的名字。每個類都有一個方法列表 methodLists ,存放著 SEL 的名字和 IMP 方法實現的映射關系如圖示。IMP 類似函數指針,指向具體的 Method 實現。

method-list.jpg

利用 Runtime 可以實現在運行時偷換 SEL 對應的方法實現 Method 或重置 IMP 方法實現,達到 hook 的目的。

method-swizzing.jpg

以上也就是大名鼎鼎的黑魔法原理(Method Swizzling),常見的用法

code.jpg

當然 Object-C 還支持動態創建對象,動態添加方法,動態添加屬性(Object-C 中當類注冊完成后無法動態添加屬性,但可以用關聯方法來模擬屬性功能,屬性的本質是 get 和 set 方法)

alloc-class.jpg

2、JavaScriptCore 方面

前端開發的同學應該知道,瀏覽器核心模塊主要是渲染引擎和 JavaScript 引擎兩部分組成。前者用于處理頁面布局,渲染及 DOM 結構等,后者用于 JavaScript 的解析、執行及 DOM 交互等。JavaScriptCore 是一種 JavaScript 引擎,主要為 webkit 提供腳本處理能力(其主要以 safari 瀏覽器為代表)。除此之外,還有著名的 Jscript(IE), SpiderMonkey(firefox)和V8(chrome)。它提供了以下主要功能:
1、Objective-C –> JavaScript (即在 Objective-C 語言環境里執行 JavaScript 代碼段、方法,創建 JavaScript 變量及變量操作等等)執行 JavaScript 代碼的方法:首先引入 JavaScriptCore.h,然后通過 JSContext 創建 JS 運行環境,再通過 evaluateScript 來執行結果

oc-javascript.jpg

需要注意 Objective-C 和 JS 數據類型之間的轉換表:

convert-type.jpg

2、JavaScript –> Objective-C(即在 JavaScript 語言環境里調用 Objective-C 公開給 JavaScript 的方法)。有 JSExport 協議和 Block 兩種方式

javascript-oc.jpg

3、內存管理和線程封裝(主要是需要注意引用和線程使用沖突)

當 JS 對象引用到 Object-C 對象(繼承了 JSExport 協議),而 Object-C 對象又引用到 JS 對象 時就會發生循環用(很少見的場景,即使真存在,也可以通過架構設計的方式來避免)

memory-leak.jpg

這個時候就需要使用到 JSManagerValue 包裝一下

memory-leak2.jpg

實際代碼如下:

jscode.jpg

至于線程沖突就涉及到 JSVirtualMachine 的理解:其實每一個 JSVirtualMachine 都管理著一個 JavaScript 虛擬機(JSContext 的載體),它運行在 Object-C 中的一個獨立線程隊列,相同的 JSVirtualMachine 共用同一個線程隊列,不同的 JSVirtualMachine 當然也就處于不同的線程隊列,它們之間無法進行數據通訊,只能通過 Object-C 來做通訊中轉,所以會出現線程沖突問題。理解了這個關鍵點,解決沖突問題就容易了。
通常初始化 JSContext 環境都會加載在一個 JSVirtualMachine 虛擬機,即使不指定 JSVirtualMachine 對象,也會默認加載一個,如下圖示:

JSVirtualMachine.jpg

3、Object-C 和 JavaScript 之間的橋接
JSPatch 中兩者之間交互依托前面的數據類型轉換表,使用最簡單的字符串傳遞方式交互信息達到動態化的目的。一句話總結:JS 傳遞字符串給 OC,OC 通過 Runtime 接口調用和替換 OC 方法,這是最基礎的原理。如下圖示:

jspatch.jpg

詳細的打怪漲經驗方式,還是去參考 bang 神的文檔,芝麻開門:
https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3

服務端原理

JSPatch 需要使用者有一個后臺可以下發和管理腳本,并且需要處理傳輸安全等部署工作,JSPatch 平臺幫你做了這些事,提供了腳本后臺托管,版本管理,保證傳輸安全等功能,讓你無需搭建一個后臺,無需關心部署操作。但還是需要了解一些服務端原理的。下載 JS 腳本只是簡單的 get 請求,這里要研究的是其中傳輸安全,灰度下發和回滾機制。

1、安全機制

這里就直接引用 bang 神的原文,如下圖示:

transport-safe.jpg

1、服務端計算出腳本文件的 MD5 值,作為這個文件的數字簽名。
2、服務端通過私鑰加密第 1 步算出的 MD5 值,得到一個加密后的 MD5 值。
3、把腳本文件和加密后的 MD5 值一起下發給客戶端。
4、客戶端拿到加密后的 MD5 值,通過保存在客戶端的公鑰解密。
5、客戶端計算腳本文件的 MD5 值。
6、對比第 4/5 步的兩個 MD5 值(分別是客戶端和服務端計算出來的 MD5 值),若相等則通過校驗。

只要通過校驗,就能確保腳本在傳輸的過程中沒有被篡改,因為第三方若要篡改腳本文件,必須計算出新的腳本文件 MD5 并用私鑰加密,客戶端公鑰才能解密出這個 MD5 值,而在服務端未泄露的情況下第三方是拿不到私鑰的。
JSPatch 平臺是用 PHP 實現的,這里筆者用 Node.js 仿照流程來模擬基礎實現原理。

node-jspatch.jpg

運行效果如下:

node-response.jpg

這里為了看效果并沒有對 data 進行加密,實際生產環境中使用時,腳本需要進行版本管理,提交 PR-Review,通過以后才能通過服務獲取,而腳本內容也是需要進行 RSA 加密的,安全第一嘛。再配合上蘋果的 ATS 要求所有APP域名都支持HTTPS傳輸(此要求 delay 了),至此已經實現了安全傳輸機制。

2、灰度下發機制

灰度下發涉及到數據上傳,分析等,甚至有的公司都已經做到了大數據挖掘的程度,JSPatch 平臺支持按用戶數量、按條件(常被用來做新功能發布或線上調試)灰度下發,其中按條件還支持后臺動態配置條件,功能很強大。詳細的可以參考 http://www.jspatch.com/Docs/rule

3、回滾機制

這部分 JSPatch 平臺并沒有詳細說明,但目前實踐中大部分都是簡單粗暴地重傳:即已下發腳本出 bug 了,就再出一個 fixed patch,重新下發。但這種方式對于日活千萬上億的 APP,是不能容忍的。這里可以提供一種使用基于 git 版本開源項目管理的方式:每一次腳本下發都提交 PR-Review,Review 通過以后 merger 再下發,一旦出錯直接 git revert。Native 端緩存最新版本和上一個版本的 patch 補丁,共兩份,當檢測到回滾發生(服務端下發的版本標識小于 Native 端版本)時,把上一個版本的 patch 標識為最新,出錯的 patch 標識為歷史版本,完成回滾操作。

后記

使用 JSPatch 已有半年多時間了,從中收獲到很多,也踩過不少坑,比如:無法替換 main 函數之前執行的類方法 +(void)load; +(void)initialize; 等,無法調用被 hook 住的源方法,但都一一趟過了,總的來說還是一個非常強大商業化工具。有感興趣的小伙伴還是強烈推薦多多閱讀 bang 神的 Wiki,傳送門:https://github.com/bang590/JSPatch/wiki

希望本文能對準備接入或者學習 JSPatch 的開發人員有所幫助。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容