OCEval-动态执行ObjectiveC的热修复方案

OCEval

需求

目前流行的 JSPatch/RN 基于JavaScriptCore提供了iOS的热修复和动态化方案。可是都必须经过下发Javascript脚原本调用Objective-C。 尤为是JSPatch,编写大量的JS代码来调用OC的方法,开发效率较低(目前能够借助语法转换器),运行效率也会打折扣。 更好的方案是直接编写Objective-C代码,来实现热修复或者动态化方案。开发效率更高,代码的执行效率也更高。javascript

在python和javascript等脚本语言里,有相似eval()函数来直接动态执行代码。因此我实现了OCEval 这个库,让咱们能直接动态执行Objective-C代码。例子以下:前端

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

为了实现跟JSPatch相似的热修复功能,增长了方法替换。咱们就能够经过下发Objective-C代码进行现有App的方法替换,来进行热修复的功能。java

//在新的imp里直接调用旧的方法实现
NSString *viewDidLoad2 = @"{\ [originalInvocation invoke];\ ";

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

OCEval甚至能够用来完整的编写一个页面或者App,并动态下发。我在iOS的Demo里实现了一个简单的页面,具体见源码。python

实现原理

C和C++的性能高,是由于编译型语言在编译期就已经生成了机器码,运行时只须要执行机器码因此执行效率高,可是动态性差。 js的性能差,是由于js的runtime引擎一般是在实际执行前进行的编译的。优势是动态性好。 像Dart和python等等均可以编译打包执行或者JIT(Just in time)执行。git

不一样于C和C++,Objective-C是动态化的语言,Objective-C的runtime利用消息发送和转发能够动态地执行任何方法。 同时Objective-C又不一样于javascript等彻底动态化的语言。 由于大多数调用是在编译期就已经决定的,编译出可执行文件(mach-O)。github

因此在OCEval里实现了一个轻量级的解释器,动态地解释Objective-C代码,同时利用OC的runtime消息转发来动态执行Objective-C的代码,就能够实现相似eval()函数的彻底动态化方式。函数

解释器

Objective-C在LLVM下的编译过程:性能

源码 -> AST -> LLVM IR(中间语言) -> LLVM Bytecode -> ASM -> Nativeui

LLVM的前端是Clang,Clang的工做是把源码变成AST语法生成树。spa

Clang的前端编译过程:

  • Preprocesser: 包括#include #import等预处理, #if,#ifdef 等条件,#define等宏定义
  • Lexer:词法分析,把文本变成token(Tokenizer)
  • Parser:语法分析,把token变成AST

可是在OCEval里,没有作得那么复杂,由于只是为了可以执行。因此只实现了词法分析和语法分析,获得语法生成树AST。

Runtime

执行的时候递归降低地执行每一条指令。这里利用的runtime主要是NSInvocation,利用methodSignature封装方法的调用惯例,跟JSPatch/RN的最终调用方式一模一样。

+ (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); //转而调用objc_msgsend
        return result;
    }
    return [self invokeWithInvocation:invocation argments:arguments];
}
复制代码

参考JSPatch,在相似[NSString stringWithFormat:]这样可变参数的方法里使用objc_msgsend。由于NSInvocation不支持不肯定的参数个数的状况。

性能

由于省去了跟JavascriptCore进行参数传递的过程,单个方法调用比JSPatch/RN快100%,耗时只有JSPatch一半,多个方法调用优点更大,耗时可能只有30%如下。

NSInvocation的调用跟Native速度差很少。可是由于动态调用很麻烦,入参出参和调用惯例都须要动态定义,同时上下文参数在内存的传递也比较慢,因此总体是比原生慢不少(动态化必要的牺牲)。

审核

我没有尝试过提交AppStore审核,可是鉴于JSPatch多次被拒绝,被拒绝的可能性极大。咱们的App确实也没有热修复的需求。

感谢

感谢JSPatch,libff,Aspect

Github 连接在 OCEval

相关文章
相关标签/搜索