導(dǎo)語(yǔ)
在iOS的發(fā)展歷程上辨泳,涌現(xiàn)了很多動(dòng)態(tài)化方案,有歷史悠久的WaxPatch動(dòng)態(tài)化方案玖院,有遠(yuǎn)近聞名的JSPatch動(dòng)態(tài)化方案漠吻。今天精神哥向大家介紹一款堪稱“史上最瘋狂”的iOS動(dòng)態(tài)化方案——OCS。
本文來(lái)自騰訊 SNG - OCS團(tuán)隊(duì)
初窺OCS
OCS是全新設(shè)計(jì)的iOS動(dòng)態(tài)化方案司恳。我們定義了一套精確描述OC語(yǔ)義的字節(jié)碼指令集(OCScript)途乃,開發(fā)了一套全自動(dòng)編譯器(OCSCompiler),實(shí)現(xiàn)了一個(gè)高性能的虛擬機(jī)(OCSVM)以及一個(gè)可以跟底層無(wú)縫對(duì)接的橋接器(OCSBridge)扔傅。我們首先使用OCS編譯器把OC源碼轉(zhuǎn)化成OCS字節(jié)碼耍共,然后通過(guò)OCS橋接器實(shí)現(xiàn)OCS虛擬機(jī)與Native運(yùn)行時(shí)的互聯(lián)烫饼,最后使用OCS虛擬機(jī)對(duì)OCS字節(jié)碼進(jìn)行解釋運(yùn)算,并驅(qū)動(dòng)Native運(yùn)行時(shí)完成邏輯的執(zhí)行试读,以此達(dá)到Native代碼動(dòng)態(tài)化的效果杠纵。OCS被用于iOS
APP安裝包減包、功能插件化钩骇、HotPatch等方方面面動(dòng)態(tài)化需求比藻。
那么,我們?yōu)槭裁匆獙?shí)現(xiàn)OCS呢倘屹?
OCS的發(fā)展背景
在手Q iPhone客戶端的發(fā)展歷史上银亲,有兩大眾所周知的剛性技術(shù)需求一直是繞之不開的,一是安裝包瘦身纽匙,二是打熱補(bǔ)丁务蝠。為了進(jìn)行減包和打熱補(bǔ)丁,我們跟很多APP一樣烛缔,先后使用WaxPatch和JSPatch這兩大神器(本文后面對(duì)同類型的使用第三方腳本語(yǔ)言作為中間語(yǔ)言實(shí)現(xiàn)的動(dòng)態(tài)化方案都簡(jiǎn)稱為XPatch)進(jìn)行了廣泛的嘗試馏段,取得了很多成果,也暴露了不少問(wèn)題:
1. 學(xué)習(xí)成本高
要進(jìn)行補(bǔ)丁/插件編寫践瓷,必須要學(xué)習(xí)并掌握對(duì)應(yīng)的腳本語(yǔ)言院喜,其次還需要學(xué)習(xí)補(bǔ)丁腳本的編寫方式,并且必須熟悉各種約定和潛規(guī)則晕翠,否則够坐,很容易掉到各種各樣的坑里去。
2. 語(yǔ)義不一致
XPatch使用的腳本語(yǔ)言的語(yǔ)義與OC的語(yǔ)義天然不一致崖面,所以機(jī)械地把Native代碼轉(zhuǎn)化成腳本元咙,很容易引入未定義行為。由于缺乏高效的調(diào)試工具巫员,Bug修復(fù)效率非常低下庶香。
3. 內(nèi)存管理不一致
XPatch使用的解釋引擎使用垃圾回收機(jī)制進(jìn)行內(nèi)存管理,而Native環(huán)境則使用MRC/ARC進(jìn)行內(nèi)存管理简识。兩者并不兼容赶掖,很容易引入各種詭異的Crash。
4. 線程管理不一致
XPatch使用的解釋引擎本身只支持單線程七扰,而Native運(yùn)行環(huán)境則支持搶占式多線程奢赂。兩者線程管理機(jī)制不可避免地相互沖突,非常容易引入隱秘的死鎖和Crash風(fēng)險(xiǎn)颈走。
5. 性能低下
XPatch對(duì)Native運(yùn)行時(shí)進(jìn)行消息發(fā)送的時(shí)候需要進(jìn)行大量的字符串解析和數(shù)據(jù)轉(zhuǎn)換操作膳灶,效率非常低下,很容易造成頁(yè)面滑動(dòng)卡頓和掉幀
OCS又是如何解決這些問(wèn)題的呢?
四大一致性原則
為了克服第三方腳本虛擬機(jī)動(dòng)態(tài)方案存在的種種問(wèn)題轧钓,我們從2015年下旬開始了OCS動(dòng)態(tài)化方案的設(shè)計(jì)與實(shí)現(xiàn)序厉。為了讓上述問(wèn)題得到有效解決, 我們提出了普適性的iOS動(dòng)態(tài)化四大一致性原則:
開發(fā)體驗(yàn)一致性毕箍;
線程管理一致性弛房;
內(nèi)存管理一致性;
語(yǔ)義定義一致性而柑。
這四大一致性原則貫穿于OCS整個(gè)設(shè)計(jì)實(shí)現(xiàn)過(guò)程文捶。OCS從無(wú)到有,從只支持基本特性到功能日趨完善媒咳,從首發(fā)到迭代升級(jí)粹排,OCS的架構(gòu)從未發(fā)生重大更改。
OCS 1.0版本于2016年8月在手Q成功上線伟葫。到目前為止,OCS已經(jīng)得到大規(guī)模代碼轉(zhuǎn)化與海量用戶使用實(shí)戰(zhàn)驗(yàn)證院促。從統(tǒng)計(jì)數(shù)據(jù)來(lái)看筏养,我們的設(shè)計(jì)目標(biāo)基本得到實(shí)現(xiàn),傳統(tǒng)腳本動(dòng)態(tài)化方案存在的種種弊端幾近得到完美的解決常拓。
OCS有哪些與眾不同之處
OCS是嚴(yán)格遵循四大一致性原則設(shè)計(jì)實(shí)現(xiàn)出來(lái)的渐溶,那么它有哪些鮮明的特點(diǎn)呢?
1. 語(yǔ)義與OC保持嚴(yán)格一致
OCS字節(jié)碼指令集語(yǔ)義與OC/的語(yǔ)義保持嚴(yán)格一一對(duì)應(yīng)關(guān)系弄抬,支持的數(shù)據(jù)類型也完全等同茎辐,運(yùn)行過(guò)程無(wú)需進(jìn)行任何轉(zhuǎn)換,因此效率非常高掂恕。
2. 自動(dòng)化工具支持
OCS擁有完善的自動(dòng)化工具鏈拖陆,OCS編譯器支持絕大多數(shù)OC/C語(yǔ)法的轉(zhuǎn)化,包括C方法懊亡、任意自定義結(jié)構(gòu)體依啰、任意自定義block。對(duì)于少數(shù)不支持的的語(yǔ)法店枣,可以準(zhǔn)確報(bào)錯(cuò)速警,引導(dǎo)用戶進(jìn)行規(guī)避,避免產(chǎn)生未定義行為鸯两。OCS編譯器還可以準(zhǔn)確識(shí)別OC/C代碼的內(nèi)存管理語(yǔ)義闷旧,可以正確生成內(nèi)存管理相關(guān)的代碼。特別對(duì)于ARC來(lái)說(shuō)钧唐,可以準(zhǔn)確生成Retain忙灼、Release代碼,正確插入到生成代碼中去钝侠。
3. 原生OC內(nèi)存管理機(jī)制
OCS虛擬機(jī)完全支持Native的內(nèi)存管理機(jī)制缀棍,可以在正確的時(shí)機(jī)執(zhí)行正確的內(nèi)存釋放邏輯宅此,絕無(wú)延遲操作,徹底杜絕了Crash和爆內(nèi)存風(fēng)險(xiǎn)的引入爬范。
4. 搶占式多線程
OCS虛擬機(jī)支持Native的搶占式多線程線程管理支持父腕,完全支持Pthread、 GCD青瀑、 NSOperationqueue璧亮、 NSThread等各種線程模型,完全避免了額外引入Crash斥难、卡頓與死鎖風(fēng)險(xiǎn)枝嘶。
5. 高性能匯編ABI
OCS橋接器根據(jù)過(guò)程調(diào)用約定實(shí)現(xiàn)自定義OCSABI,使得虛擬機(jī)與Native底層實(shí)現(xiàn)直接通信哑诊,保證Native代碼動(dòng)態(tài)化引入額外性能損耗最小化群扶,使得OCS動(dòng)態(tài)化不僅普遍適用于普通邏輯轉(zhuǎn)化,對(duì)于對(duì)性能要求苛刻的有復(fù)雜排版的滑動(dòng)列表镀裤,OCS也有極佳的表現(xiàn)竞阐,動(dòng)態(tài)化后掉幀率增長(zhǎng)量幾乎可以忽略不計(jì)。
那么OCS框架的各個(gè)組成部分到底是怎樣的呢暑劝?
OCS是一套四位一體的自研動(dòng)態(tài)化方案骆莹,其中包含細(xì)節(jié)非常多。限于文章篇幅和讀者的流量担猛,我們下面僅對(duì)核心模塊做簡(jiǎn)單的介紹幕垦。
OCScript
OCScript與OC存在語(yǔ)義上的一一對(duì)應(yīng)關(guān)系。跟OC一樣傅联,OCScript也是圖靈完備性的語(yǔ)言先改。下面我們看看OCScript的指令集概況
基本運(yùn)算指令使得OCScript具有加、減蒸走、乘盏道、除、移位等各種基本算術(shù)载碌、邏輯運(yùn)算能力猜嘱。
內(nèi)存操作指令使得OCScript具有在內(nèi)存中的讀寫各種數(shù)據(jù)的能力;
地址指跳轉(zhuǎn)指令使得OCScript具有分支判斷與控制轉(zhuǎn)移的能力嫁艇;
強(qiáng)類型轉(zhuǎn)換指令使得OCScript具有對(duì)數(shù)據(jù)類型進(jìn)行強(qiáng)制轉(zhuǎn)化能力朗伶;
OC調(diào)用指令使得OCScript具有指令級(jí)別的消息發(fā)送能力,根本性地提高了執(zhí)行效率步咪。
OCScript中每個(gè)指令編碼占一個(gè)字節(jié)论皆,所以儲(chǔ)存空間非常緊湊。
OCSCompiler
我們基于Clang構(gòu)建了編譯器
在編譯器中,我們根據(jù)AST來(lái)生成OCScript点晴。舉個(gè)例子
圖中左側(cè)的源碼使用Clang轉(zhuǎn)成右側(cè)的AST語(yǔ)法樹感凤。然后遍歷每個(gè)AST節(jié)點(diǎn),即可生成對(duì)應(yīng)的OCScript字節(jié)碼
它的具體含義可以根據(jù)對(duì)應(yīng)的符號(hào)信息翻譯得到
從上面的匯編符號(hào)信息我們不難看出粒督,OCScript的語(yǔ)義跟OC Native代碼的語(yǔ)義完全一致陪竿。
OCScript字節(jié)碼按如下文件格式進(jìn)行存儲(chǔ)
然后由OCSVM加載執(zhí)行。
OCSVM
OCSVM是OCS動(dòng)態(tài)化方案的核心屠橄,它不僅具備了解釋執(zhí)行OCScript的能力族跛,它還承擔(dān)了不可或缺的內(nèi)存管理任務(wù)和線程管理任務(wù)
內(nèi)存管理系統(tǒng)
我們深入剖析了操作系統(tǒng)和OC運(yùn)行時(shí)的內(nèi)存管理相關(guān)的自動(dòng)控制原理,構(gòu)建了跟Native一致的內(nèi)存管理機(jī)制锐墙,確保所有數(shù)據(jù)類型的內(nèi)存得到妥善管理與正確釋放礁哄。
解釋執(zhí)行系統(tǒng)
我們以圖靈機(jī)作為計(jì)算模型,構(gòu)造了具有強(qiáng)大通用計(jì)算能力溪北、可以正確識(shí)別OC語(yǔ)義的專有計(jì)算系統(tǒng)
線程管理系統(tǒng)
為了支持跟Native一致的支持搶占式多線程的線程管理機(jī)制桐绒,我們構(gòu)造了多核心并發(fā)多任務(wù)執(zhí)行框架
一個(gè)運(yùn)算核心對(duì)應(yīng)一個(gè)線程
我們經(jīng)過(guò)精心設(shè)計(jì)的運(yùn)算核心結(jié)構(gòu)非常緊湊,對(duì)內(nèi)存的占用量幾乎可以忽略不計(jì)之拨,就算有上百個(gè)線程在APP中運(yùn)行茉继,也表示毫無(wú)壓力。
OCSBridge
橋接器主要由三部分組成
OCSBridge各個(gè)部分都基于OCSABI來(lái)構(gòu)建敦锌,OCSABI使用匯編來(lái)實(shí)現(xiàn)馒疹,確保了虛擬機(jī)可以與Native最底層進(jìn)行直接高速通信
值得一提的是佳簸,Block Bridge實(shí)現(xiàn)最復(fù)雜乙墙,它必須符合“OCS——Native互調(diào)完備性矩陣”
顯然只有這樣,才能實(shí)現(xiàn)任意自定義Block的雙向互調(diào)生均。
OCSEngine
擁有虛擬機(jī)和橋接器后听想,我們就可以構(gòu)造出強(qiáng)大的動(dòng)態(tài)化引擎,并植入到任意APP中去马胧。
OCSEngine框架圖如下
配置系統(tǒng)汉买,解析動(dòng)態(tài)化配置文件,使用OC Runtime時(shí)特性佩脊,為進(jìn)程動(dòng)態(tài)添加/替換OC 方法蛙粘。
運(yùn)算系統(tǒng),職責(zé)由OCSVM承擔(dān)威彰。
通信系統(tǒng)出牧,職責(zé)由OCSBridge承擔(dān)。
OCSEngine啟動(dòng)流程如下
OCSEnging完成啟動(dòng)后歇盼,就可以和Native環(huán)境融為一體舔痕,流暢地完成各種邏輯執(zhí)行。
OCS化Native代碼流程
只需要使用OCS轉(zhuǎn)化宏把代碼包裹起來(lái),就可以由OCSCompiler自動(dòng)完成轉(zhuǎn)化流程伯复。無(wú)論是幾百行代碼的迷你小模塊慨代,還是上萬(wàn)行代碼的超級(jí)巨無(wú)霸類,均可一網(wǎng)打盡啸如,便捷性不言而喻侍匙。
性能測(cè)試
OCS上線前,我們邀請(qǐng)了手Q性能專項(xiàng)測(cè)試團(tuán)隊(duì)進(jìn)行對(duì)OCS進(jìn)行了性能專項(xiàng)測(cè)試组底。
我們使用手Q掛件商城作為測(cè)試案例丈积,主要原因如下:
掛件商城是XPatch插件實(shí)現(xiàn)的,并且保留了完整的Native代碼债鸡。
掛件商城使用了布局非常復(fù)雜江滨、內(nèi)容豐富的的列表,這為測(cè)試滑動(dòng)流暢度提供了絕佳的場(chǎng)景厌均。
測(cè)試方法:分別針對(duì)不同實(shí)現(xiàn)分別編寫不同測(cè)試包唬滑,使用相同的測(cè)試環(huán)境進(jìn)行測(cè)試。
測(cè)試機(jī)器:iPhone 5s棺弊。
各項(xiàng)測(cè)試數(shù)據(jù)如下圖表:
物理內(nèi)存(單位:M)
CPU平均值
滑動(dòng)流暢度
加載耗時(shí)(單位:S)
從以上各數(shù)據(jù)都不難看出晶密,OCS性能跟Native相當(dāng)接近。特別是對(duì)用戶體驗(yàn)影響最大的滑動(dòng)流暢度方面模她,OCS的表現(xiàn)幾乎跟Native持平稻艰,事實(shí)上,我們從肉眼上已經(jīng)難以分辨出頁(yè)面到底是用Native實(shí)現(xiàn)還是OCS實(shí)現(xiàn)的了侈净。
海量用戶實(shí)戰(zhàn)
OCS自9月份在手Q上線尊勿,目前已經(jīng)已經(jīng)有近20個(gè)業(yè)務(wù)、數(shù)萬(wàn)行代碼使用OCS進(jìn)行了動(dòng)態(tài)化畜侦,安裝包減量接近1M元扔。所轉(zhuǎn)業(yè)務(wù)日PV高達(dá)千萬(wàn)級(jí)別,并快速向億級(jí)別挺進(jìn)旋膳。同時(shí)澎语,Crash率呈逐步下降趨勢(shì)(OCS的本身的Bug不斷得到收斂),最新線上版本全量OCS插件日Crash率(Crash總次數(shù)/打開總次數(shù))為十萬(wàn)分之二验懊。另外擅羞,尚未收到性能測(cè)試部門或外部用戶遇到OCS業(yè)務(wù)出現(xiàn)卡頓、死鎖相關(guān)反饋义图。
OCS插件日PV(單位:萬(wàn))
OCS化代碼行數(shù)(單位:行)
實(shí)戰(zhàn)數(shù)據(jù)也表明减俏,在四大一致性原則指導(dǎo)下設(shè)計(jì)實(shí)現(xiàn)的OCS動(dòng)態(tài)化框架,其易用性歌溉、穩(wěn)定性垄懂、和流暢性骑晶,都經(jīng)得起大規(guī)模代碼轉(zhuǎn)化與海量用戶驗(yàn)證的實(shí)戰(zhàn)考驗(yàn)。
團(tuán)隊(duì)介紹
我們OCS聯(lián)合開發(fā)小組六位成員來(lái)自鵝廠各個(gè)部門草慧,有白銀峰桶蛔、周劉紀(jì)、董超這樣才華橫溢的移動(dòng)互聯(lián)網(wǎng)新生代漫谷,又有殷詩(shī)壯仔雷、張愷、徐國(guó)歡這樣混跡碼界多年的老油條舔示。然而年齡與部門距離并沒(méi)有形成不可逾越的鴻溝碟婆,對(duì)技術(shù)創(chuàng)新的濃厚興趣與對(duì)提升生產(chǎn)效率的本能饑渴使得我們并肩作戰(zhàn)的決心從未動(dòng)搖,我們潛心鉆研攻克了OCS一個(gè)又一個(gè)的難題惕稻。
OCS團(tuán)隊(duì)也開通了微信公眾號(hào):OCScript竖共,歡迎大家關(guān)注。
常見問(wèn)題與解答
問(wèn):OCSEngine的安裝包大小如何俺祠?
答:OCSEngine編到APP中的安裝包增量不到200k公给。對(duì),你沒(méi)看錯(cuò)蜘渣,不到200k淌铐。
問(wèn):OCS為什么不用JS作為中間語(yǔ)言呢?
答:這是一個(gè)好問(wèn)題蔫缸,從圖靈等價(jià)的角度來(lái)看腿准,我們可以使用JS、Lua甚至甲骨文來(lái)作為中間語(yǔ)言拾碌,只要你喜歡就好吐葱。但經(jīng)過(guò)理論驗(yàn)證,在我們看來(lái)倦沧,OCScript會(huì)是更好的選擇唇撬。為什么呢它匕?事實(shí)上我們可以使用計(jì)算理論和信息論來(lái)進(jìn)行論證展融,但限于篇幅,我們就不在這里展開豫柬。后續(xù)我們會(huì)專門寫一篇論文性質(zhì)的文章來(lái)討論這一課題告希,文章題目初步擬為《第三方腳本語(yǔ)言作中間語(yǔ)言的iOS動(dòng)態(tài)化方案探索,從快速入門到果斷放棄》烧给,后續(xù)歡迎關(guān)注燕偶。
問(wèn):OCS有開放計(jì)劃嗎?
答:我們非常愿意與業(yè)界同行相互交流學(xué)習(xí)础嫡,一同促進(jìn)iOS動(dòng)態(tài)化方案的進(jìn)步指么。對(duì)于方案原理酝惧,我們已經(jīng)在實(shí)踐開放計(jì)劃,而對(duì)于具體工程實(shí)現(xiàn)開放伯诬,由于受到法律約束晚唇,目前還沒(méi)有明確時(shí)間表,但我們也在積極推動(dòng)之中盗似。
結(jié)語(yǔ)
曾經(jīng)有人說(shuō)過(guò):如果你問(wèn)一個(gè)程序員此時(shí)此刻正在在干什么哩陕,他一定會(huì)毫不猶豫地告訴你,他不是在改Bug赫舒,就是在去改Bug的路上悍及。
Bug是程序員幸福生活的頭號(hào)殺手。讓我們感到欣慰的是接癌,OCS從上線到現(xiàn)在心赶,都沒(méi)有收到OCS框架本身給使用OCS進(jìn)行動(dòng)態(tài)化的業(yè)務(wù)引入重大Bug的反饋(零星引入Bug的反饋基本是由于框架實(shí)現(xiàn)過(guò)程中手抖引入的)。
OCS不僅是一種嶄新的“拿起就用”的動(dòng)態(tài)化方案缺猛,更是成一種“用完就走”的可依賴的開發(fā)方式园担。通過(guò)OCS,我們捍衛(wèi)了程序員作為普通勞動(dòng)者應(yīng)有的合法權(quán)利:每一個(gè)人都可以更加高效地完成動(dòng)態(tài)化工作枯夜,并在忙碌了一天后弯汰,可以放下工作上的紛擾,花多點(diǎn)時(shí)間陪陪身邊的伴侶和家人湖雹,并得到充分的休息咏闪,保證第二天可以滿血復(fù)原,繼續(xù)著改變世界的工作摔吏。
最后鸽嫂,愿OCS可以早日給業(yè)界帶來(lái)力所能及的貢獻(xiàn)。