OCS:史上最瘋狂的 iOS 動態化方案
導語
在iOS的發展歷程上,涌現了很多動態化方案,有歷史悠久的 WaxPatch 動態化方案,有遠近聞名的 JSPatch 動態化方案。今天精神哥向大家介紹一款堪稱“史上最瘋狂”的iOS動態化方案—— OCS 。
本文來自騰訊 SNG - OCS團隊
初窺OCS
OCS是全新設計的iOS動態化方案。我們定義了一套精確描述OC語義的字節碼指令集(OCScript),開發了一套全自動編譯器(OCSCompiler),實現了一個高性能的虛擬機(OCSVM)以及一個可以跟底層無縫對接的橋接器(OCSBridge)。我們首先使用OCS編譯器把OC源碼轉化成OCS字節碼,然后通過OCS橋接器實現OCS虛擬機與Native運行時的互聯,最后使用OCS虛擬機對OCS字節碼進行解釋運算,并驅動Native運行時完成邏輯的執行,以此達到Native代碼動態化的效果。OCS被用于iOS APP安裝包減包、功能插件化、HotPatch等方方面面動態化需求。
那么,我們為什么要實現OCS呢?
OCS的發展背景
在手Q iPhone客戶端的發展歷史上,有兩大眾所周知的剛性技術需求一直是繞之不開的,一是安裝包瘦身,二是打熱補丁。為了進行減包和打熱補丁,我們跟很多APP一樣,先后使用WaxPatch和JSPatch這兩大神器( 本文后面對同類型的使用第三方腳本語言作為中間語言實現的動態化方案都簡稱為XPatch )進行了廣泛的嘗試,取得了很多成果,也暴露了不少問題:
1. 學習成本高
要進行補丁/插件編寫,必須要學習并掌握對應的腳本語言,其次還需要學習補丁腳本的編寫方式,并且必須熟悉各種約定和潛規則,否則,很容易掉到各種各樣的坑里去。
2. 語義不一致
XPatch使用的腳本語言的語義與OC的語義天然不一致,所以機械地把Native代碼轉化成腳本,很容易引入未定義行為。由于缺乏高效的調試工具,Bug修復效率非常低下。
3. 內存管理不一致
XPatch使用的解釋引擎使用垃圾回收機制進行內存管理,而Native環境則使用MRC/ARC進行內存管理。兩者并不兼容,很容易引入各種詭異的Crash。
4. 線程管理不一致
XPatch使用的解釋引擎本身只支持單線程,而Native運行環境則支持搶占式多線程。兩者線程管理機制不可避免地相互沖突,非常容易引入隱秘的死鎖和Crash風險。
5. 性能低下
XPatch對Native運行時進行消息發送的時候需要進行大量的字符串解析和數據轉換操作,效率非常低下,很容易造成頁面滑動卡頓和掉幀
OCS又是如何解決這些問題的呢?
四大一致性原則
為了克服第三方腳本虛擬機動態方案存在的種種問題,我們從2015年下旬開始了OCS動態化方案的設計與實現。為了讓上述問題得到有效解決, 我們提出了普適性的iOS動態化四大一致性原則:
-
開發體驗一致性;
-
線程管理一致性;
-
內存管理一致性;
-
語義定義一致性。
這四大一致性原則貫穿于OCS整個設計實現過程。OCS從無到有,從只支持基本特性到功能日趨完善,從首發到迭代升級,OCS的架構從未發生重大更改。
OCS 1.0版本于2016年8月在手Q成功上線。到目前為止,OCS已經得到大規模代碼轉化與海量用戶使用實戰驗證。從統計數據來看,我們的設計目標基本得到實現,傳統腳本動態化方案存在的種種弊端幾近得到完美的解決。
OCS有哪些與眾不同之處
OCS是嚴格遵循四大一致性原則設計實現出來的,那么它有哪些鮮明的特點呢?
1. 語義與OC保持嚴格一致
OCS字節碼指令集語義與OC/的語義保持嚴格一一對應關系,支持的數據類型也完全等同,運行過程無需進行任何轉換,因此效率非常高。
2. 自動化工具支持
OCS擁有完善的自動化工具鏈,OCS編譯器支持絕大多數OC/C語法的轉化,包括C方法、任意自定義結構體、任意自定義block。對于少數不支持的的語法,可以準確報錯,引導用戶進行規避,避免產生未定義行為。OCS編譯器還可以準確識別OC/C代碼的內存管理語義,可以正確生成內存管理相關的代碼。特別對于ARC來說,可以準確生成Retain、Release代碼,正確插入到生成代碼中去。
3. 原生OC內存管理機制
OCS虛擬機完全支持Native的內存管理機制,可以在正確的時機執行正確的內存釋放邏輯,絕無延遲操作,徹底杜絕了Crash和爆內存風險的引入。
4. 搶占式多線程
OCS虛擬機支持Native的搶占式多線程線程管理支持,完全支持Pthread、 GCD、 NSOperationqueue、 NSThread等各種線程模型,完全避免了額外引入Crash、卡頓與死鎖風險。
5. 高性能匯編ABI
OCS橋接器根據過程調用約定實現自定義OCSABI,使得虛擬機與Native底層實現直接通信,保證Native代碼動態化引入額外性能損耗最小化,使得OCS動態化不僅普遍適用于普通邏輯轉化,對于對性能要求苛刻的有復雜排版的滑動列表,OCS也有極佳的表現,動態化后掉幀率增長量幾乎可以忽略不計。
那么OCS框架的各個組成部分到底是怎樣的呢?
OCS是一套四位一體的自研動態化方案,其中包含細節非常多。限于文章篇幅和讀者的流量,我們下面僅對核心模塊做簡單的介紹。
OCScript
OCScript與OC存在語義上的一一對應關系。跟OC一樣,OCScript也是圖靈完備性的語言。下面我們看看OCScript的指令集概況
-
基本運算指令使得OCScript具有加、減、乘、除、移位等各種基本算術、邏輯運算能力。
-
內存操作指令使得OCScript具有在內存中的讀寫各種數據的能力;
-
地址指跳轉指令使得OCScript具有分支判斷與控制轉移的能力;
-
強類型轉換指令使得OCScript具有對數據類型進行強制轉化能力;
-
OC調用指令使得OCScript具有指令級別的消息發送能力,根本性地提高了執行效率。
OCScript中每個指令編碼占一個字節,所以儲存空間非常緊湊。
OCSCompiler
我們基于Clang構建了編譯器
在編譯器中,我們根據AST來生成OCScript。舉個例子
圖中左側的源碼使用Clang轉成右側的AST語法樹。然后遍歷每個AST節點,即可生成對應的OCScript字節碼
它的具體含義可以根據對應的符號信息翻譯得到
從上面的匯編符號信息我們不難看出,OCScript的語義跟OC Native代碼的語義完全一致。
OCScript字節碼按如下文件格式進行存儲
然后由OCSVM加載執行。
OCSVM
OCSVM是OCS動態化方案的核心,它不僅具備了解釋執行OCScript的能力,它還承擔了不可或缺的內存管理任務和線程管理任務
內存管理系統
我們深入剖析了操作系統和OC運行時的內存管理相關的自動控制原理,構建了跟Native一致的內存管理機制,確保所有數據類型的內存得到妥善管理與正確釋放。
解釋執行系統
我們以圖靈機作為計算模型,構造了具有強大通用計算能力、可以正確識別OC語義的專有計算系統
線程管理系統
為了支持跟Native一致的支持搶占式多線程的線程管理機制,我們構造了多核心并發多任務執行框架
一個運算核心對應一個線程
我們經過精心設計的運算核心結構非常緊湊,對內存的占用量幾乎可以忽略不計,就算有上百個線程在APP中運行,也表示毫無壓力。
OCSBridge
橋接器主要由三部分組成
OCSBridge各個部分都基于OCSABI來構建,OCSABI使用匯編來實現,確保了虛擬機可以與Native最底層進行直接高速通信
值得一提的是,Block Bridge實現最復雜,它必須符合“OCS——Native互調完備性矩陣”
顯然只有這樣,才能實現任意自定義Block的雙向互調。
OCSEngine
擁有虛擬機和橋接器后,我們就可以構造出強大的動態化引擎,并植入到任意APP中去。
OCSEngine框架圖如下
-
配置系統,解析動態化配置文件,使用OC Runtime時特性,為進程動態添加/替換OC 方法。
-
運算系統,職責由OCSVM承擔。
-
通信系統,職責由OCSBridge承擔。
OCSEngine啟動流程如下
OCSEnging完成啟動后,就可以和Native環境融為一體,流暢地完成各種邏輯執行。
OCS化Native代碼流程
只需要使用OCS轉化宏把代碼包裹起來,就可以由OCSCompiler自動完成轉化流程。無論是幾百行代碼的迷你小模塊,還是上萬行代碼的超級巨無霸類,均可一網打盡,便捷性不言而喻。
性能測試
OCS上線前,我們邀請了手Q性能專項測試團隊進行對OCS進行了性能專項測試。
我們使用手Q掛件商城作為測試案例,主要原因如下:
-
掛件商城是XPatch插件實現的,并且保留了完整的Native代碼。
-
掛件商城使用了布局非常復雜、內容豐富的的列表,這為測試滑動流暢度提供了絕佳的場景。
測試方法:分別針對不同實現分別編寫不同測試包,使用相同的測試環境進行測試。
測試機器:iPhone 5s。
各項測試數據如下圖表:
物理內存(單位:M)
CPU平均值
滑動流暢度
加載耗時(單位:S)
從以上各數據都不難看出,OCS性能跟Native相當接近。特別是對用戶體驗影響最大的滑動流暢度方面,OCS的表現幾乎跟Native持平,事實上,我們從肉眼上已經難以分辨出頁面到底是用Native實現還是OCS實現的了。
海量用戶實戰
OCS自9月份在手Q上線,目前已經已經有近20個業務、數萬行代碼使用OCS進行了動態化,安裝包減量接近1M。所轉業務日PV高達千萬級別,并快速向億級別挺進。同時,Crash率呈逐步下降趨勢(OCS的本身的Bug不斷得到收斂),最新線上版本全量OCS插件日Crash率(Crash總次數/打開總次數)為十萬分之二。另外,尚未收到性能測試部門或外部用戶遇到OCS業務出現卡頓、死鎖相關反饋。
OCS插件日PV(單位:萬)
OCS化代碼行數(單位:行)
實戰數據也表明,在四大一致性原則指導下設計實現的OCS動態化框架,其易用性、穩定性、和流暢性,都經得起大規模代碼轉化與海量用戶驗證的實戰考驗。
團隊介紹
我們OCS聯合開發小組六位成員來自鵝廠各個部門,有白銀峰、周劉紀、董超這樣才華橫溢的移動互聯網新生代,又有殷詩壯、張愷、徐國歡這樣混跡碼界多年的老油條。然而年齡與部門距離并沒有形成不可逾越的鴻溝,對技術創新的濃厚興趣與對提升生產效率的本能饑渴使得我們并肩作戰的決心從未動搖,我們潛心鉆研攻克了OCS一個又一個的難題。
OCS團隊也開通了微信公眾號 : OCScript ,歡迎大家關注。
常見問題與解答
問:OCSEngine的安裝包大小如何?
答:OCSEngine編到APP中的安裝包增量不到200k。對,你沒看錯,不到200k。
問:OCS為什么不用JS作為中間語言呢?
答:這是一個好問題,從圖靈等價的角度來看,我們可以使用JS、Lua甚至甲骨文來作為中間語言,只要你喜歡就好。但經過理論驗證,在我們看來,OCScript會是更好的選擇。為什么呢?事實上我們可以使用計算理論和信息論來進行論證,但限于篇幅,我們就不在這里展開。后續我們會專門寫一篇論文性質的文章來討論這一課題,文章題目初步擬為《第三方腳本語言作中間語言的iOS動態化方案探索,從快速入門到果斷放棄》,后續歡迎關注。
問:OCS有開放計劃嗎?
答:我們非常愿意與業界同行相互交流學習,一同促進iOS動態化方案的進步。對于方案原理,我們已經在實踐開放計劃,而對于具體工程實現開放,由于受到法律約束,目前還沒有明確時間表,但我們也在積極推動之中。
結語
曾經有人說過:如果你問一個程序員此時此刻正在在干什么,他一定會毫不猶豫地告訴你,他不是在改Bug,就是在去改Bug的路上。
Bug是程序員幸福生活的頭號殺手。讓我們感到欣慰的是,OCS從上線到現在,都沒有收到OCS框架本身給使用OCS進行動態化的業務引入重大Bug的反饋(零星引入Bug的反饋基本是由于框架實現過程中手抖引入的)。
OCS不僅是一種嶄新的“拿起就用”的動態化方案,更是成一種“用完就走”的可依賴的開發方式。通過OCS,我們捍衛了程序員作為普通勞動者應有的合法權利:每一個人都可以更加高效地完成動態化工作,并在忙碌了一天后,可以放下工作上的紛擾,花多點時間陪陪身邊的伴侶和家人,并得到充分的休息,保證第二天可以滿血復原,繼續著改變世界的工作。
最后,愿OCS可以早日給業界帶來力所能及的貢獻。
來自:http://mp.weixin.qq.com/s/zctwM2Wf8c6_sxT_0yZvXg