OCEval-動(dòng)態(tài)執(zhí)行ObjectiveC的熱修復(fù)方案

OCEval

需求

目前流行的 JSPatch/RN 基于JavaScriptCore提供了iOS的熱修復(fù)和動(dòng)態(tài)化方案撒顿。但是都必須通過下發(fā)Javascript腳本來調(diào)用Objective-C活合。 尤其是JSPatch婿屹,編寫大量的JS代碼來調(diào)用OC的方法菩掏,開發(fā)效率較低(目前可以借助語法轉(zhuǎn)換器)霎烙,運(yùn)行效率也會(huì)打折扣募寨。
更好的方案是直接編寫Objective-C代碼,來實(shí)現(xiàn)熱修復(fù)或者動(dòng)態(tài)化方案审葬。開發(fā)效率更高深滚,代碼的執(zhí)行效率也更高。

在python和javascript等腳本語言里涣觉,有類似eval()函數(shù)來直接動(dòng)態(tài)執(zhí)行代碼痴荐。所以我實(shí)現(xiàn)了OCEval 這個(gè)庫,讓我們能直接動(dòng)態(tài)執(zhí)行Objective-C代碼官册。例子如下:

NSString *inputStr = @"return 1 + 3 <= 4 && [NSString string] != nil;";
NSNumber *result = [OCEval eval:inputStr]; // result: @(YES)

為了實(shí)現(xiàn)跟JSPatch類似的熱修復(fù)功能生兆,增加了方法替換。我們就可以通過下發(fā)Objective-C代碼進(jìn)行現(xiàn)有App的方法替換膝宁,來進(jìn)行熱修復(fù)的功能鸦难。

//在新的imp里直接調(diào)用舊的方法實(shí)現(xiàn)
NSString *viewDidLoad2 = @"{\
[originalInvocation invoke];\
";

[OCEval hookClass:@"ViewController"
         selector:@"viewDidLoad"
         argNames:@[]
          isClass:NO
   implementation:viewDidLoad2];

OCEval甚至可以用來完整的編寫一個(gè)頁面或者App,并動(dòng)態(tài)下發(fā)员淫。我在iOS的Demo里實(shí)現(xiàn)了一個(gè)簡單的頁面合蔽,具體見源碼。

實(shí)現(xiàn)原理

C和C++的性能高满粗,是因?yàn)榫幾g型語言在編譯期就已經(jīng)生成了機(jī)器碼,運(yùn)行時(shí)只需要執(zhí)行機(jī)器碼所以執(zhí)行效率高愚争,但是動(dòng)態(tài)性差映皆。
js的性能差挤聘,是因?yàn)閖s的runtime引擎通常是在實(shí)際執(zhí)行前進(jìn)行的編譯的。優(yōu)點(diǎn)是動(dòng)態(tài)性好捅彻。
像Dart和python等等都可以編譯打包執(zhí)行或者JIT(Just in time)執(zhí)行组去。

不同于C和C++,Objective-C是動(dòng)態(tài)化的語言步淹,Objective-C的runtime利用消息發(fā)送和轉(zhuǎn)發(fā)可以動(dòng)態(tài)地執(zhí)行任何方法从隆。
同時(shí)Objective-C又不同于javascript等完全動(dòng)態(tài)化的語言。 因?yàn)榇蠖鄶?shù)調(diào)用是在編譯期就已經(jīng)決定的缭裆,編譯出可執(zhí)行文件(mach-O)键闺。

所以在OCEval里實(shí)現(xiàn)了一個(gè)輕量級的解釋器,動(dòng)態(tài)地解釋Objective-C代碼澈驼,同時(shí)利用OC的runtime消息轉(zhuǎn)發(fā)來動(dòng)態(tài)執(zhí)行Objective-C的代碼辛燥,就可以實(shí)現(xiàn)類似eval()函數(shù)的完全動(dòng)態(tài)化方式。

解釋器

Objective-C在LLVM下的編譯過程:

源碼 -> AST -> LLVM IR(中間語言) -> LLVM Bytecode -> ASM -> Native

LLVM的前端是Clang缝其,Clang的工作是把源碼變成AST語法生成樹挎塌。

Clang的前端編譯過程:

  • Preprocesser: 包括#include #import等預(yù)處理, #if,#ifdef 等條件,#define等宏定義
  • Lexer:詞法分析内边,把文本變成token(Tokenizer)
  • Parser:語法分析榴都,把token變成AST

但是在OCEval里,沒有做得那么復(fù)雜漠其,因?yàn)橹皇菫榱四軌驁?zhí)行嘴高。所以只實(shí)現(xiàn)了詞法分析和語法分析,得到語法生成樹AST辉懒。

Runtime

執(zhí)行的時(shí)候遞歸下降地執(zhí)行每一條指令阳惹。這里利用的runtime主要是NSInvocation,利用methodSignature封裝方法的調(diào)用慣例眶俩,跟JSPatch/RN的最終調(diào)用方式如出一轍莹汤。

+ (id)invokeWithCaller:(id)caller selectorName:(NSString *)selectorName argments:(NSArray *)arguments
{
    SEL selector = NSSelectorFromString(selectorName);
    NSInvocation *invocation;
    NSMethodSignature *methodSignature = [caller methodSignatureForSelector:selector];
    invocation= [NSInvocation invocationWithMethodSignature:methodSignature];
    [invocation setTarget:caller];
    [invocation setSelector:selector];
    NSUInteger numberOfArguments = methodSignature.numberOfArguments;
    NSInteger inputArguments = [arguments count];
    if (inputArguments > numberOfArguments - 2) {
        id result = invokeVariableParameterMethod(arguments, methodSignature, caller, selector); //轉(zhuǎn)而調(diào)用objc_msgsend
        return result;
    }
    return [self invokeWithInvocation:invocation argments:arguments];
}

參考JSPatch,在類似[NSString stringWithFormat:]這樣可變參數(shù)的方法里使用objc_msgsend颠印。因?yàn)?code>NSInvocation不支持不確定的參數(shù)個(gè)數(shù)的情況纲岭。

性能

因?yàn)槭∪チ烁鶭avascriptCore進(jìn)行參數(shù)傳遞的過程,單個(gè)方法調(diào)用比JSPatch/RN快100%线罕,耗時(shí)只有JSPatch一半止潮,多個(gè)方法調(diào)用優(yōu)勢更大,耗時(shí)可能只有30%以下钞楼。

NSInvocation的調(diào)用跟Native速度差不多喇闸。但是因?yàn)閯?dòng)態(tài)調(diào)用很麻煩,入?yún)⒊鰠⒑驼{(diào)用慣例都需要?jiǎng)討B(tài)定義,同時(shí)上下文參數(shù)在內(nèi)存的傳遞也比較慢燃乍,所以整體是比原生慢很多(動(dòng)態(tài)化必要的犧牲)唆樊。

審核

我沒有嘗試過提交AppStore審核,但是鑒于JSPatch屢次被拒絕刻蟹,被拒絕的可能性極大逗旁。我們的App確實(shí)也沒有熱修復(fù)的需求。

感謝

感謝JSPatch,libff,Aspect

Github 鏈接在 OCEval

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舆瘪,一起剝皮案震驚了整個(gè)濱河市片效,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌英古,老刑警劉巖淀衣,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哺呜,居然都是意外死亡舌缤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門某残,熙熙樓的掌柜王于貴愁眉苦臉地迎上來国撵,“玉大人,你說我怎么就攤上這事玻墅〗檠溃” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵澳厢,是天一觀的道長环础。 經(jīng)常有香客問我,道長剩拢,這世上最難降的妖魔是什么线得? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮徐伐,結(jié)果婚禮上贯钩,老公的妹妹穿的比我還像新娘。我一直安慰自己办素,他們只是感情好角雷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著性穿,像睡著了一般勺三。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上需曾,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天吗坚,我揣著相機(jī)與錄音祈远,去河邊找鬼。 笑死商源,一個(gè)胖子當(dāng)著我的面吹牛绊含,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炊汹,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逃顶!你這毒婦竟也來了讨便?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤以政,失蹤者是張志新(化名)和其女友劉穎霸褒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盈蛮,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡废菱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抖誉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殊轴。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖袒炉,靈堂內(nèi)的尸體忽然破棺而出旁理,到底是詐尸還是另有隱情,我是刑警寧澤我磁,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布孽文,位于F島的核電站,受9級特大地震影響夺艰,放射性物質(zhì)發(fā)生泄漏芋哭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一郁副、第九天 我趴在偏房一處隱蔽的房頂上張望减牺。 院中可真熱鬧,春花似錦霞势、人聲如沸烹植。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽草雕。三九已至,卻和暖如春固以,著一層夾襖步出監(jiān)牢的瞬間墩虹,已是汗流浹背嘱巾。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诫钓,地道東北人旬昭。 一個(gè)月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像菌湃,于是被迫代替她去往敵國和親问拘。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容