JSPatch源码解析

JSPatch一款优秀的热更新框架,最近由于各种原因被苹果封杀了,虽然我们开发者暂时不能使用这种方式来进行来热更新,但是代码还是值得我们好好学习一下的,尤其是里面对于Runtime的使用,我们可以把它当做是Runtime的实践教材。

当然热更新的方式还有很多,Weex,React Native, Hybrid ... 好像苹果现在并没有禁止JavaScriptCore引擎下的代码下发。

其实框架的实现原理,作者已经写的很详细了,本文主要是从源码的角度来解析实现过程。

下面根据调用流程来看一下,具体的是怎么实现的。
本文JS代码的调试是使用的Safari,具体步骤:
Safari->开发->Simulator->选择当前执行的js

使用方式

举个栗子,比如有一个bug:

- (IBAction)btnClick:(id)sender {
    NSArray *arrTest = @[@"1"];
    @try {
        NSString *strCrash = [arrTest objectAtIndex:2];
        NSLog(@"strCrash %@", strCrash);
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Click" message:@"Success" delegate:nil cancelButtonTitle:@"Yes" otherButtonTitles:nil, nil];
        [alert show];
    } @catch (NSException *exception) {
        NSLog(@"exception is %@", exception);
    } @finally {
        NSLog(@"finally");
    }
}

经典的数组越界问题,当然这里添加了try catch并不会崩溃,但是会抛出异常。

使用JSPatch下发main.js代码如下:

require("UIAlertView");

defineClass("ViewController", {
btnClick: function(sender) {
var arrTest = ["arrTest"];
var strCrash = arrTest[0];

var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("JSPatchAmend", "Success", null, "Yes", null, null);
    alert.show();
}
}, {}, {});

当然我们现在没有JS代码下发的逻辑,所以模拟一下JS代码的执行,main.js先加到工程中:

- (void)testJSPatch {
    NSString *strJsPath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
    NSLog(@"strJsPath %@", strJsPath);
    JSValue *resultValue = [JPEngine evaluateScriptWithPath:strJsPath];
    NSLog(@"resultValue %@", resultValue);
}

追踪代码

首先我们调用的是JPEngine类的evaluateScriptWithPath方法

+ (JSValue *)evaluateScriptWithPath:(NSString *)filePath
{
    _scriptRootDir = [filePath stringByDeletingLastPathComponent];
    return [self _evaluateScriptWithPath:filePath];
}

+ (JSValue *)_evaluateScriptWithPath:(NSString *)filePath
{
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 获取JS代码 字符串
    return [self _evaluateScript:script withSourceURL:[NSURL URLWithString:[filePath lastPathComponent]]];
}

+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL
{
    if (!script || ![JSContext class]) {
        _exceptionBlock(@"script is nil");
        return nil;
    }
    [self startEngine]; // 开启引擎, 开始初始化一些参数
    
    if (!_regex) {
        _regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil];
    }
    // js字符串代码格式转换:新增了try catch,防止崩溃,使用正则 替换方法__c(),方法的调用统一走__c()方法
    NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{\n%@\n}catch(e){_OC_catch(e.message, e.stack)}})();", [_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]];
    @try {
        if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
            return [_context evaluateScript:formatedScript withSourceURL:resourceURL]; // 调用JS
        } else {
            return [_context evaluateScript:formatedScript];
        }
    }
    @catch (NSException *exception) {
        _exceptionBlock([NSString stringWithFormat:@"%@", exception]);
    }
    return nil;
}

这里获取到JS代码字符串之后,开始调用+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL 方法执行调用。

方法逻辑:

  • 调用startEngine 方法(初始化参数,并调用JSPatch.js)

  • 使用正则替换替换所有方法调用为 __c() 的形式,并添加try-catch语句防止崩溃

  • 执行main.js代码,并使用_exceptionBlock抛出异常

看一下startEngine方法

+ (void)startEngine
{
    if (![JSContext class] || _context) { // 如果已经初始化_context就返回,不需要重新初始化
        return;
    }
    
    JSContext *context = [[JSContext alloc] init];
    
    // 提前注册JS方法调用,js调用对应方法的时候回调给OC方法
#ifdef DEBUG
    context[@"po"] = ^JSValue*(JSValue *obj) {
        id ocObject = formatJSToOC(obj);
        return [JSValue valueWithObject:[ocObject description] inContext:_context];
    };

    context[@"bt"] = ^JSValue*() {
        return [JSValue valueWithObject:_JSLastCallStack inContext:_context];
    };
#endif

    context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
        return defineClass(classDeclaration, instanceMethods, classMethods); // 类名, 实例方法,类方法
    };

    context[@"_OC_defineProtocol"] = ^(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) {
        return defineProtocol(protocolDeclaration, instProtocol,clsProtocol);
    };
    
    context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { // call instace method
        return callSelector(nil, selectorName, arguments, obj, isSuper);
    };
    context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { // call class method
        return callSelector(className, selectorName, arguments, nil, NO);
    };
    
   ...
       
    context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
        NSLog(@"%@", exception);
        _exceptionBlock([NSString stringWithFormat:@"js exception: %@", exception]);
    };
    
    _nullObj = [[NSObject alloc] init];
    context[@"_OC_null"] = formatOCToJS(_nullObj);
    
    _context = context;
    
    // 初始化
    _nilObj = [[NSObject alloc] init];
    _JSMethodSignatureLock = [[NSLock alloc] init];
    _JSMethodForwardCallLock = [[NSRecursiveLock alloc] init];
    _registeredStruct = [[NSMutableDictionary alloc] init];
    _currInvokeSuperClsName = [[NSMutableDictionary alloc] init];
    
#if TARGET_OS_IPHONE
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    
    // 运行JSPatch.js代码
    NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"JSPatch" ofType:@"js"];
    if (!path) _exceptionBlock(@"can't find JSPatch.js");
    NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding];
    
    if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
        [_context evaluateScript:jsCore withSourceURL:[NSURL URLWithString:@"JSPatch.js"]];
    } else {
        [_context evaluateScript:jsCore];
    }
}

方法逻辑:

  • 提前注册JS调用OC的方法,具体如下
_OC_defineClass, 定义类
_OC_defineProtocol,定义协议
_OC_callI,调用实例方法
_OC_callC,调用类方法
_OC_formatJSToOC,格式化JS为OC类型
_OC_formatOCToJS,格式化OC为JS类型
_OC_getCustomProps,获取属性get方法
_OC_setCustomProps,获取属性set方法
__weak,__weak属性
__strong, __strong 属性
_OC_superClsName,获取父类名
resourcePath, 获取js文件路径
dispatch_after,GCD延时调用
dispatch_async_main,GCD异步主队列
dispatch_sync_main,GCD同步主队列
dispatch_async_global_queue,GCD全局队列
releaseTmpObj,释放tmp对象
_OC_log, log信息
_OC_null, 空对象
  • 初始化一些变量 _nilObj,_JSMethodSignatureLock,_JSMethodForwardCallLock...

  • 加载JSPatch.js代码

执行main.js代码的时候使用的是JSpatch.js里面的defineClass()方法

看一下JSPatch.js中的defineClass()方法

  // declaration: 类名,父类,协议的描述,cls:supercls
  // properties: { 方法名:JS的方法实现 }
  // instMethods
  global.defineClass = function(declaration, properties, instMethods, clsMethods) {
    var newInstMethods = {}, newClsMethods = {}
    if (!(properties instanceof Array)) { // properties 不是数组,是字典类型: {方法名:方法体},直接赋值给 instMethods,然后置空
      clsMethods = instMethods
      instMethods = properties
      properties = null
    }

    /*
     逻辑:如果是属性那么使用数组[property1,property2], 再动态获取set,get方法然后放到instMethods字典中,也就是instMethods中存的是{方法名:方法实现}
     */
    if (properties) { // 此时 properties 应该是数组,那么处理OC属性Property相关,
      properties.forEach(function(name){
        if (!instMethods[name]) {
          instMethods[name] = _propertiesGetFun(name); // 设置property的get方法
        }
        var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1); // set方法
        if (!instMethods[nameOfSet]) {
          instMethods[nameOfSet] = _propertiesSetFun(name); // 设置property的set方法
        }
      });
    }
  
    // 获取真实的类名
    var realClsName = declaration.split(':')[0].trim()  // split 把一个字符串分割成字符串数组

    _formatDefineMethods(instMethods, newInstMethods, realClsName)
    _formatDefineMethods(clsMethods, newClsMethods, realClsName)

    var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods) // oc构造类,返回类名,父类名 返回值:@{@"cls": className, @"superCls": superClassName};
    var className = ret['cls']
    var superCls = ret['superCls']

    _ocCls[className] = {
      instMethods: {},
      clsMethods: {},
    }

    if (superCls.length && _ocCls[superCls]) {
      for (var funcName in _ocCls[superCls]['instMethods']) { // 如果父类中有这个实例方法,直接赋值给当前类
        _ocCls[className]['instMethods'][funcName] = _ocCls[superCls]['instMethods'][funcName]
      }
      for (var funcName in _ocCls[superCls]['clsMethods']) {  // 如果父类中有这个类例方法,直接赋值给当前类
        _ocCls[className]['clsMethods'][funcName] = _ocCls[superCls]['clsMethods'][funcName]
      }
    }
  
    // className: OC定义的类名,instMethods:实例方法{方法名:方法实现},instMethods:解析declaration获取的真实类名
    // 对js代码进行了一次包装,_wrapLocalMethod
    _setupJSMethod(className, instMethods, 1, realClsName)
    _setupJSMethod(className, clsMethods, 0, realClsName)

    return require(className) // 返回的是: {__clsName: 类名}
  }

方法逻辑:

  • 处理传入的JS方法

    properties传入的不是数组类型,那么就是方法类型:{方法名:方法实现},直接赋值给instMethods,并置nil。

  • 处理属性方法数组

    properties传入的是数组类型,那么处理这些属性,动态生成set,get方法,使用的是Runtime的关联对象方法。

  • 格式化实例方法,类方法

    格式化类方法,实例方法,将方法封装成数组 [1, function()], 1 为 originMethod.length(var originMethod = methods[methodName] )根据OC代码来看应该是参数个数, function()方法对之前的方法进行了封装,封装方法跟下面的_setupJSMethod一样。

  • 调用OC的_OC_defineClass方法执行定义类

    下面解析这个OC方法,返回类型是:
    {"cls": className, "superCls": superClassName};

  • 如果本地缓存_ocCls中有父类,将继承所有父类方法。

  • 设置JS方法,进行了一次包装

  • 最后返回类名的字典:{__clsName: 类名}

穿插中间调用JPEngine.m中的_OC_defineClass()方法

context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
        return defineClass(classDeclaration, instanceMethods, classMethods); // 类名, 实例方法,类方法
    };
    
static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
    // classDeclaration: 原始的类名 + :父类 + '<>'协议
    // 通过runtime定义类,并返回类名,父类名
    NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];
    
    NSString *className;
    NSString *superClassName;
    NSString *protocolNames; //  class:superclass 扫描获取className,superClassName,protocolNames
    [scanner scanUpToString:@":" intoString:&className];
    if (!scanner.isAtEnd) {
        scanner.scanLocation = scanner.scanLocation + 1;
        [scanner scanUpToString:@"<" intoString:&superClassName];
        if (!scanner.isAtEnd) {
            scanner.scanLocation = scanner.scanLocation + 1;
            [scanner scanUpToString:@">" intoString:&protocolNames];
        }
    }
    
    if (!superClassName) superClassName = @"NSObject"; // 默认父类都是NSObject
    className = trim(className); // 去空格
    superClassName = trim(superClassName);
    
    NSArray *protocols = [protocolNames length] ? [protocolNames componentsSeparatedByString:@","] : nil;
    
    Class cls = NSClassFromString(className);
    if (!cls) { // 转换不了此类,动态新增一个NSObject的子类
        Class superCls = NSClassFromString(superClassName);
        if (!superCls) {
            _exceptionBlock([NSString stringWithFormat:@"can't find the super class %@", superClassName]);
            return @{@"cls": className};
        }
        cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
        objc_registerClassPair(cls);
    }
    
    if (protocols.count > 0) { // 添加协议列表
        for (NSString* protocolName in protocols) {
            Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]);
            class_addProtocol (cls, protocol);
        }
    }
    
    for (int i = 0; i < 2; i ++) { // 第一次循环对类进行操作,第二次循环对元类进行操作
        BOOL isInstance = i == 0;
        JSValue *jsMethods = isInstance ? instanceMethods: classMethods; // 方法
        
        // instanceMethods 字典 key:方法名, value:数组(0:参数个数 1:js代码???)
        Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String); // 类名
        NSDictionary *methodDict = [jsMethods toDictionary];
        for (NSString *jsMethodName in methodDict.allKeys) {
            JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
            /*
             1,function () {
             try {
             var args = _formatOCToJS(Array.prototype.slice.call(arguments))
             var lastSelf = global.self
             global.self = args[0]
             if (global.self) global.self.__realClsName = realClsName
             args.splice(0,1) // 删除第一个元素
             var ret = originMethod.apply(originMethod, args)
             global.self = lastSelf
             return ret
             } catch(e) {
             _OC_catch(e.message, e.stack)
             }
             }
             */
            int numberOfArg = [jsMethodArr[0] toInt32];
            NSString *selectorName = convertJPSelectorString(jsMethodName);
            
            if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) { // 这个地方一个参数可以添加一个':',如果是多个参数呢?怎么添加?
                selectorName = [selectorName stringByAppendingString:@":"];
            }
            
            JSValue *jsMethod = jsMethodArr[1]; // 来自js代码
            if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) { // 类中含有这个方法,替换方法,跳转拦截调用
                overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);
            } else { // 类中没有这个方法, 可能是协议
                BOOL overrided = NO;
                for (NSString *protocolName in protocols) { // 如果有协议,全部添加
                    char *types = methodTypesInProtocol(protocolName, selectorName, isInstance, YES);
                    if (!types) types = methodTypesInProtocol(protocolName, selectorName, isInstance, NO);
                    if (types) {
                        overrideMethod(currCls, selectorName, jsMethod, !isInstance, types);
                        free(types);
                        overrided = YES;
                        break;
                    }
                }
                if (!overrided) {
                    if (![[jsMethodName substringToIndex:1] isEqualToString:@"_"]) {
                        NSMutableString *typeDescStr = [@"@@:" mutableCopy];
                        for (int i = 0; i < numberOfArg; i ++) {
                            [typeDescStr appendString:@"@"];
                        }
                        overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);
                    }
                }
            }
        }
    }
    
    // 新增两个方法: setProp:  getProp: forKey:
    class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
    class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");

    return @{@"cls": className, @"superCls": superClassName};
}

方法逻辑:

  • 解析classDeclaration中的类名,父类,协议
  • 没有父类,默认是NSObject
  • 如果这个类不是系统类,调用runtime的objc_registerClassPair新建
  • 如果有protocols,动态添加协议列表
  • 添加实例方法,类方法

双重for循环,第一次是对类操作,添加实例方法,第二次是对元类操作,添加类方法。

  • 遍历方法列表,获取jsMethodArr第0个位置:参数个数,为方法动态添加":"

  • 如果本类中,已经有这个方法,那么执行overrideMethod()方法替换,跳转拦截调用,下面解析

  • 如果本类中,没有这个方法,可能是协议,先获取协议方法的参数类型typeDescription,再执行overrideMethod()方法

  • 如果不是本类中的方法,也不是协议,那么需要获取到这个方法的参数类型typeDescription,再执行overrideMethod()

  • 为类新增set,get方法,统一使用关联对象关联property

  • 返回类名,父类名

overrideMethod()到底做了些什么?

// 方法替换方法 _objc_msgForward,所有的方法全部都跳转到拦截调用
/*
 * 新增了一个ORIGFunc方法,方法实现是原来的方法实现,添加到本类中
 * 新增一个JSFunc名字, 将方法名添加_JP前缀作为key,将JS代码作为value存入_JSOverideMethods中, 为以后JS调用做存储
 */
static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
{
    SEL selector = NSSelectorFromString(selectorName);
    
    if (!typeDescription) { // 如果没有传typeDescription,就是类中有这个方法,那么可以直接通过方法获取typeDescription
        Method method = class_getInstanceMethod(cls, selector);
        typeDescription = (char *)method_getTypeEncoding(method);
    }
    
    IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;
    
    IMP msgForwardIMP = _objc_msgForward; // 用_objc_msgForward函数指针代替imp,_objc_msgForward是用于消息转发的。也就是跳转拦截调用
    #if !defined(__arm64__)
        if (typeDescription[0] == '{') {
            //In some cases that returns struct, we should use the '_stret' API:
            //http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
            //NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription.
            NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:typeDescription];
            if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) {
                msgForwardIMP = (IMP)_objc_msgForward_stret;
            }
        }
    #endif

    if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
        // 如果不是本地的forwardInvocation实现,新增JPForwardInvocation方法到类中,原来的forwardInvocation方法实现更改为ORIGforwardInvocation下。
        IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
        if (originalForwardImp) {
            class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
        }
    }

    [cls jp_fixMethodSignature]; // fix bug
    if (class_respondsToSelector(cls, selector)) { // 类中含有这个方法,将原来的方法新增一个ORIG前缀,新增到类中, ORIG方法的实现是原方法
        NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName]; // ORIGFunc
        SEL originalSelector = NSSelectorFromString(originalSelectorName);
        if(!class_respondsToSelector(cls, originalSelector)) {
            class_addMethod(cls, originalSelector, originalImp, typeDescription);
        }
    }
    
    NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; // _JPFunc
    
    _initJPOverideMethods(cls); // 初始化_JSOverideMethods
    _JSOverideMethods[cls][JPSelectorName] = function;
    // 将方法名添加_JP前缀作为key,将js代码作为value存入_JSOverideMethods中
    // 存储JS代码: _JSOverideMethods字段: {类 = {"_JPFunc" = "function实现"}}
    
    
    // Replace the original selector at last, preventing threading issus when
    // the selector get called during the execution of `overrideMethod`
    class_replaceMethod(cls, selector, msgForwardIMP, typeDescription); // 将方法替换为_objc_msgForward,调用直接跳转拦截调用

方法逻辑:

  • 如果没有传typeDescription,就是类中有这个方法,那么可以直接通过方法获取typeDescription
  • 获取方法是原来实现originalImp,以及设置拦截调用IMP:_objc_msgForward
  • 如果不是本地的forwardInvocation实现,新增JPForwardInvocation方法到类中,原来的forwardInvocation方法实现更改为ORIGforwardInvocation下
  • 如果类中含有这个方法,将原来的方法新增一个ORIG前缀,新增到类中, ORIG方法的实现是原方法
  • 保存一份方法:将方法名新增_JP前缀,添加到全局对象_JSOverideMethods字典中:{类 = {"_JPFunc" = "function实现"}}
  • 执行方法替换,将当前方法selector的实现替换成msgForwardIMP,也就是肯定会跳转拦截调用,在拦截调用中捕获参数信息。

执行到这里,我们看到JS里面保存了一份方法的实现,OC里面也存了一份。
JS中:_ocCls
OC中:_JSOverideMethods


再次触发崩溃按钮,跟踪一下代码

- (IBAction)btnClick:(id)sender 

根据上面的解析,应该会走拦截调用方法,而我们已经将拦截调用的方法实现IMP替换成JPForwardInvocation()方法。接下来看一下这个方法。

拦截调用方法JPForwardInvocation()

static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
{
    BOOL deallocFlag = NO;
    id slf = assignSlf;
    NSMethodSignature *methodSignature = [invocation methodSignature]; // 获取参数,返回值相关信息
    NSInteger numberOfArguments = [methodSignature numberOfArguments]; // 参数个数
    
    NSString *selectorName = NSStringFromSelector(invocation.selector); // 获取方法名
    NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; // _JPFunc
    JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName); // 获取之前存在 _JSOverideMethods字典中对应JS方法的实现
    if (!jsFunc) { // 如果没有这个方法实现,那么跳转系统自己的拦截调用方法,或者是工程中有实现的拦截调用方法,但是问题是如果有方法实现,那么工程中的拦截调用如果重写了,那么就不会执行ForwardInvocation方法了!!!!
        JPExecuteORIGForwardInvocation(slf, selector, invocation);
        return;
    }
    
    NSMutableArray *argList = [[NSMutableArray alloc] init]; // 存类型数组
    if ([slf class] == slf) { // slf 是 类 类型
        [argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
    } else if ([selectorName isEqualToString:@"dealloc"]) { // selectorName是dealloc方法
        [argList addObject:[JPBoxing boxAssignObj:slf]];
        deallocFlag = YES;
    } else { // 正常的对象类型
        [argList addObject:[JPBoxing boxWeakObj:slf]];
    }
    
    // 对参数进行解析,从i=2开始,就是对参数解析。  methodSignature: 返回值, 各个参数
    for (NSUInteger i = 2; i < numberOfArguments; i++) {
        const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
        switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
        
            ...
            
            case '@': {  // 对象类型
                __unsafe_unretained id arg;
                [invocation getArgument:&arg atIndex:i]; // 获取当前对象,例子中是UIButton类型 >
                if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {
                    [argList addObject:(arg ? [arg copy]: _nilObj)]; // NSBlock 对象需要执行copy,猜测是防止释放吧
                } else {
                    [argList addObject:(arg ? arg: _nilObj)]; // 添加到argList
                }
                break;
            }
           
            ...
            
            default: {
                NSLog(@"error type %s", argumentType);
                break;
            }
        }
    }
    
    if (_currInvokeSuperClsName[selectorName]) { // 处理父类方法
        Class cls = NSClassFromString(_currInvokeSuperClsName[selectorName]);
        NSString *tmpSelectorName = [[selectorName stringByReplacingOccurrencesOfString:@"_JPSUPER_" withString:@"_JP"] stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"_JP"];
        if (!_JSOverideMethods[cls][tmpSelectorName]) {
            NSString *ORIGSelectorName = [selectorName stringByReplacingOccurrencesOfString:@"SUPER_" withString:@"ORIG"];
            [argList removeObjectAtIndex:0];
            id retObj = callSelector(_currInvokeSuperClsName[selectorName], ORIGSelectorName, [JSValue valueWithObject:argList inContext:_context], [JSValue valueWithObject:@{@"__obj": slf, @"__realClsName": @""} inContext:_context], NO);
            id __autoreleasing ret = formatJSToOC([JSValue valueWithObject:retObj inContext:_context]);
            [invocation setReturnValue:&ret];
            return;
        }
    }
    
    NSArray *params = _formatOCToJSList(argList); // 转换成JS类型的对象,格式:[{"__clsName": 类名, "__obj": 对象}, ...]
    char returnType[255];
    strcpy(returnType, [methodSignature methodReturnType]); // 获取返回值类型 此处v:void
    
    // Restore the return type
    if (strcmp(returnType, @encode(JPDouble)) == 0) { // 根据JS类型转换double
        strcpy(returnType, @encode(double));
    }
    if (strcmp(returnType, @encode(JPFloat)) == 0) { // 根据JS类型转换float
        strcpy(returnType, @encode(float));
    }

    // 处理返回值类型
    switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
       
        ... // 此处省略占篇幅的宏定义

        case 'v': {
//            JP_FWD_RET_CALL_JS // 由于用宏不好调试,替换成下面代码
            
            JSValue *jsval;
            [_JSMethodForwardCallLock lock]; // 加锁
            jsval = [jsFunc callWithArguments:params]; // 执行js方法
            [_JSMethodForwardCallLock unlock]; // 解锁
            while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) {
                NSArray *args = nil;
                JSValue *cb = jsval[@"cb"];
                if ([jsval hasProperty:@"sel"]) {
                    id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO);
                    args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]];
                }
                [_JSMethodForwardCallLock lock];
                jsval = [cb callWithArguments:args];
                [_JSMethodForwardCallLock unlock];
            }
            
            break;
        }
            
        case '{': {
            NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]);
            #define JP_FWD_RET_STRUCT(_type, _funcSuffix) \
            if ([typeString rangeOfString:@#_type].location != NSNotFound) {    \
                JP_FWD_RET_CALL_JS \
                _type ret = [jsval _funcSuffix]; \
                [invocation setReturnValue:&ret];\
                break;  \
            }
            JP_FWD_RET_STRUCT(CGRect, toRect)
            JP_FWD_RET_STRUCT(CGPoint, toPoint)
            JP_FWD_RET_STRUCT(CGSize, toSize)
            JP_FWD_RET_STRUCT(NSRange, toRange)
            
            @synchronized (_context) {
                NSDictionary *structDefine = _registeredStruct[typeString];
                if (structDefine) {
                    size_t size = sizeOfStructTypes(structDefine[@"types"]);
                    JP_FWD_RET_CALL_JS
                    void *ret = malloc(size);
                    NSDictionary *dict = formatJSToOC(jsval);
                    getStructDataWithDict(ret, dict, structDefine);
                    [invocation setReturnValue:ret];
                    free(ret);
                }
            }
            break;
        }
        default: {
            break;
        }
    }
    
    if (_pointersToRelease) {
        for (NSValue *val in _pointersToRelease) {
            void *pointer = NULL;
            [val getValue:&pointer];
            CFRelease(pointer);
        }
        _pointersToRelease = nil;
    }
    
    if (deallocFlag) {
        slf = nil;
        Class instClass = object_getClass(assignSlf);
        Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc"));
        void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
        originalDealloc(assignSlf, NSSelectorFromString(@"dealloc"));
    }
}

上面代码中有些...是省略了部分代码

方法逻辑:

  • 获取当前替换方法调用方法的参数,拦截调用的主要作用在于此。否则直接使用Method Swizzle即可,何必再走拦截调用呢。

  • 通过在方法中添加_JP前缀,获取之前存在_JSOverideMethods中对应的JS方法实现,如果为空直接调用原来的拦截调用方法。

  • 将类,对象,各个参数转换为OC对象存入argList数组中

  • 处理父类方法

  • 处理返回值

  • 根据返回值类型,调用JS代码[jsFunc callWithArguments:params],也就是此处会调用之前封装过的JS代码,执行JS中的btnClick:方法,这个地方比较绕。封装过的JS代码不是很理解。JS功底比较好的同学可以指教一下。

执行我们main.js里面btnClick:方法

btnClick: function(sender) {
var arrTest = ["arrTest"];
var strCrash = arrTest[0];

var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("JSPatchAmend", "Success", null, "Yes", null, null);
    alert.show();
}

其实已经替换成了

{
var arrTest = ["arrTest"];
var strCrash = arrTest[0];

var alert = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")("JSPatchAmend", "Success", null, "Yes", null, null);
    alert.__c("show")();
}

我们把crash代码替换掉,并执行了一个原生的Alert,这里统一的方法调用都是调用__c()

__C()方法的调用

疑问:JS如何去调用__c()方法呢?

  for (var method in _customMethods) { // __c
    if (_customMethods.hasOwnProperty(method)) {
      // Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
      // Object.prototype 属性表示 Object 的原型对象。
      Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false})
    }
  }

上面for循环我们可以看到,这里将_customMethods字典里面的key都会设置成这个对象的属性,由于这段代码没有调试出来,所以暂时理解为所有对象都添加__c、super、performSelectorInOC、performSelector方法,而__c方法实现是一个匿名函数, 返回值又是一个匿名函数

下面为_customMethods字典的内容:

  var _customMethods = { // 字典对象,存 __c: function
    __c: function(methodName) {
      var slf = this

      if (slf instanceof Boolean) {
        return function() {
          return false
        }
      }
      if (slf[methodName]) {
        return slf[methodName].bind(slf);
      }

      if (!slf.__obj && !slf.__clsName) { // 未定义抛出异常
        throw new Error(slf + '.' + methodName + ' is undefined')
      }
      if (slf.__isSuper && slf.__clsName) { // 如果是调用父类方法
          slf.__clsName = _OC_superClsName(slf.__obj.__realClsName ? slf.__obj.__realClsName: slf.__clsName); // 通过调用oc的[cls superclass] 方法获取父类的名字
      }
      var clsName = slf.__clsName
      if (clsName && _ocCls[clsName]) { // 有缓存,返回缓存的方法
        var methodType = slf.__obj ? 'instMethods': 'clsMethods'  // 通过__obj判断是实例方法还是类方法
        if (_ocCls[clsName][methodType][methodName]) {
          slf.__isSuper = 0;
          return _ocCls[clsName][methodType][methodName].bind(slf) // // 返回的是之前缓存的方法
        }
      }

      return function(){
        var args = Array.prototype.slice.call(arguments) // 转换成数组, arguments 怎么获取的??
        return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper) // 返回方法调用的结果
      }
    },

    super: function() {
      var slf = this
      if (slf.__obj) {
        slf.__obj.__realClsName = slf.__realClsName;
      }
      return {__obj: slf.__obj, __clsName: slf.__clsName, __isSuper: 1}
    },

    performSelectorInOC: function() {
      var slf = this
      var args = Array.prototype.slice.call(arguments)
      return {__isPerformInOC:1, obj:slf.__obj, clsName:slf.__clsName, sel: args[0], args: args[1], cb: args[2]}
    },

    performSelector: function() {
      var slf = this
      var args = Array.prototype.slice.call(arguments)
      return _methodFunc(slf.__obj, slf.__clsName, args[0], args.splice(1), slf.__isSuper, true)
    }
  }

注意:
方法调用例如:UIAlertView.__c("fucName")("param")
第一个调用为方法名:("fucName"),参数为fucName,执行的是以__c 为key的匿名函数,此时获取的返回值是另一个匿名函数,再执行("param")则是调用的返回值的匿名函数,参数为param。

解析一下__c()匿名函数的逻辑:

  • 做了异常处理

  • 处理父类方法

  • 如果这个JS方法在缓存_ocCls字典中,那么直接返回

  • 如果没有缓存,那么调用_methodFunc方法

下面看下_methodFunc方法

_methodFunc()方法

  var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
    var selectorName = methodName
    if (!isPerformSelector) { // 如果不是performselector,用正则对selectorName进行字符替换:__ 替换 -,- 替换 _,有':', 在最后添加上':'
      methodName = methodName.replace(/__/g, "-") // __ 替换 -
      selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_") // - 替换 _
      var marchArr = selectorName.match(/:/g) // 有':', 在最后添加上':'
      var numOfArgs = marchArr ? marchArr.length : 0
      if (args.length > numOfArgs) {
        selectorName += ":"
      }
    }
    var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
                         _OC_callC(clsName, selectorName, args) // 调用oc的生成方法:实例方法还是类方法
    return _formatOCToJS(ret)
  }

作用:通过调用OC方法,实现方法调用,并返回调用结果

方法逻辑:

  • 首先执行方法替换:
    __ 替换 -,- 替换 _,有':', 在最后添加上':'
  • 根据是否是实例对象来调用OC的方法_OC_callI(实例方法),_OC_callC(类方法)

  • 获取返回值,并格式转换为JS类型

下面看下OC的方法调用_OC_callI,_OC_callC

OC调用方法执行_OC_callI/_OC_callC

    context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { // call instace method
        return callSelector(nil, selectorName, arguments, obj, isSuper); // 返回值类型: {"__clsName": 类名; "__obj": 对象}
    };
    context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { // call class method
        return callSelector(className, selectorName, arguments, nil, NO);
    };
    

可以看到,这两个方法都是调用的callSelector()方法。

下面看一下callSelector方法的实现:

static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper)
{
    NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString];
   
    if (instance) {
        instance = formatJSToOC(instance);
        if (class_isMetaClass(object_getClass(instance))) { // 是元类
            className = NSStringFromClass((Class)instance);
            instance = nil;
        } else if (!instance || instance == _nilObj || [instance isKindOfClass:[JPBoxing class]]) {
            return @{@"__isNil": @(YES)};
        }
    }
    id argumentsObj = formatJSToOC(arguments); // 转换成oc对象数组
    
    if (instance && [selectorName isEqualToString:@"toJS"]) {
        if ([instance isKindOfClass:[NSString class]] || [instance isKindOfClass:[NSDictionary class]] || [instance isKindOfClass:[NSArray class]] || [instance isKindOfClass:[NSDate class]]) {
            return _unboxOCObjectToJS(instance);
        }
    }

    Class cls = instance ? [instance class] : NSClassFromString(className); // 传的是类或者对象
    SEL selector = NSSelectorFromString(selectorName);
    
    NSString *superClassName = nil;
    if (isSuper) { // 是否是父类的方法
        NSString *superSelectorName = [NSString stringWithFormat:@"SUPER_%@", selectorName];
        SEL superSelector = NSSelectorFromString(superSelectorName);
        
        Class superCls;
        if (realClsName.length) {
            Class defineClass = NSClassFromString(realClsName);
            superCls = defineClass ? [defineClass superclass] : [cls superclass];
        } else {
            superCls = [cls superclass];
        }
        
        Method superMethod = class_getInstanceMethod(superCls, selector);
        IMP superIMP = method_getImplementation(superMethod);
        
        class_addMethod(cls, superSelector, superIMP, method_getTypeEncoding(superMethod));
        
        NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
        JSValue *overideFunction = _JSOverideMethods[superCls][JPSelectorName];
        if (overideFunction) {
            overrideMethod(cls, superSelectorName, overideFunction, NO, NULL);
        }
        
        selector = superSelector;
        superClassName = NSStringFromClass(superCls);
    }
    
    
    NSMutableArray *_markArray;
    
    NSInvocation *invocation;
    NSMethodSignature *methodSignature;
    if (!_JSMethodSignatureCache) {
        _JSMethodSignatureCache = [[NSMutableDictionary alloc]init];
    }
    if (instance) { // 实例方法
        [_JSMethodSignatureLock lock];
        if (!_JSMethodSignatureCache[cls]) {
            _JSMethodSignatureCache[(id)cls] = [[NSMutableDictionary alloc]init]; // 初始化key
        }
        methodSignature = _JSMethodSignatureCache[cls][selectorName]; // 新增缓存机制,提升效率
        if (!methodSignature) {
            methodSignature = [cls instanceMethodSignatureForSelector:selector]; //
            methodSignature = fixSignature(methodSignature);
            _JSMethodSignatureCache[cls][selectorName] = methodSignature;
        }
        [_JSMethodSignatureLock unlock];
        if (!methodSignature) {
            _exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for instance %@", selectorName, instance]);
            return nil;
        }
        invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
        [invocation setTarget:instance];
    } else { // 类方法
        methodSignature = [cls methodSignatureForSelector:selector]; //
        methodSignature = fixSignature(methodSignature); // fix bug
        if (!methodSignature) {
            _exceptionBlock([NSString stringWithFormat:@"unrecognized selector %@ for class %@", selectorName, className]);
            return nil;
        }
        invocation= [NSInvocation invocationWithMethodSignature:methodSignature];
        [invocation setTarget:cls];
    }
    [invocation setSelector:selector];
    
    NSUInteger numberOfArguments = methodSignature.numberOfArguments;
    NSInteger inputArguments = [(NSArray *)argumentsObj count];
    if (inputArguments > numberOfArguments - 2) {
        // calling variable argument method, only support parameter type `id` and return type `id`
        id sender = instance != nil ? instance : cls;
        id result = invokeVariableParameterMethod(argumentsObj, methodSignature, sender, selector); // 调用objc_msgSend方法
        return formatOCToJS(result);
    }
    
    for (NSUInteger i = 2; i < numberOfArguments; i++) { // 参数的处理,注意从第二个argument开始:0是方法名,1是':'
        const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
        id valObj = argumentsObj[i-2];
        switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
                
                ...
                
            case '#': {
                if ([valObj isKindOfClass:[JPBoxing class]]) {
                    Class value = [((JPBoxing *)valObj) unboxClass];
                    [invocation setArgument:&value atIndex:i];
                    break;
                }
            }
            default: {
                if (valObj == _nullObj) {
                    valObj = [NSNull null];
                    [invocation setArgument:&valObj atIndex:i];
                    break;
                }
                if (valObj == _nilObj ||
                    ([valObj isKindOfClass:[NSNumber class]] && strcmp([valObj objCType], "c") == 0 && ![valObj boolValue])) {
                    valObj = nil;
                    [invocation setArgument:&valObj atIndex:i];
                    break;
                }
                if ([(JSValue *)arguments[i-2] hasProperty:@"__isBlock"]) {
                    JSValue *blkJSVal = arguments[i-2];
                    Class JPBlockClass = NSClassFromString(@"JPBlock");
                    if (JPBlockClass && ![blkJSVal[@"blockObj"] isUndefined]) {
                        __autoreleasing id cb = [JPBlockClass performSelector:@selector(blockWithBlockObj:) withObject:[blkJSVal[@"blockObj"] toObject]];
                        [invocation setArgument:&cb atIndex:i];
                    } else {
                        __autoreleasing id cb = genCallbackBlock(arguments[i-2]);
                        [invocation setArgument:&cb atIndex:i];
                    }
                } else {
                    [invocation setArgument:&valObj atIndex:i];
                }
            }
        }
    }
    
    if (superClassName) _currInvokeSuperClsName[selectorName] = superClassName;
    [invocation invoke]; // 方法调用
    if (superClassName) [_currInvokeSuperClsName removeObjectForKey:selectorName]; // 方法调用完又移除了缓存的父类方法名
    if ([_markArray count] > 0) {
        for (JPBoxing *box in _markArray) {
            void *pointer = [box unboxPointer];
            id obj = *((__unsafe_unretained id *)pointer);
            if (obj) {
                @synchronized(_TMPMemoryPool) {
                    [_TMPMemoryPool setObject:obj forKey:[NSNumber numberWithInteger:[(NSObject*)obj hash]]];
                }
            }
        }
    }
    
    char returnType[255];
    strcpy(returnType, [methodSignature methodReturnType]);
    
    // Restore the return type
    if (strcmp(returnType, @encode(JPDouble)) == 0) { // 如果是JPDouble结构体,返回double的编码d
        strcpy(returnType, @encode(double));
    }
    if (strcmp(returnType, @encode(JPFloat)) == 0) {
        strcpy(returnType, @encode(float));
    }

    id returnValue;
    if (strncmp(returnType, "v", 1) != 0) { // 不是void类型
        if (strncmp(returnType, "@", 1) == 0) { // id 类型
            void *result;
            [invocation getReturnValue:&result]; // 获取返回值result
            
            //For performance, ignore the other methods prefix with alloc/new/copy/mutableCopy
            if ([selectorName isEqualToString:@"alloc"] || [selectorName isEqualToString:@"new"] ||
                [selectorName isEqualToString:@"copy"] || [selectorName isEqualToString:@"mutableCopy"]) {
                returnValue = (__bridge_transfer id)result; // 这几个参数alloc/new/copy/mutableCopy会生成新对象,并且引用计数是1,需要我们持有,否则会提前释放?释放时机在哪?
            } else { // 一般类型的返回值:id
                returnValue = (__bridge id)result;
            }
            return formatOCToJS(returnValue);
            
        } else {
            switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
                    
                ...
                
                case '#': {
                    Class result;
                    [invocation getReturnValue:&result];
                    returnValue = formatOCToJS([JPBoxing boxClass:result]);
                    break;
                }
            }
            return returnValue;
        }
    }
    return nil;
}

由于比较长,我们删掉了部分代码。

方法逻辑:

  • 这里根据调用对象的类型来进行判断是否调用实例方法,还是类方法。并对参数进行转换成OC数组

  • 处理父类方法

  • 使用NSInvocation来执行方法调用,注意,类方法使用[cls methodSignatureForSelector:selector],而实例方法使用 [cls instanceMethodSignatureForSelector:selector]来初始化methodSignature。这里使用_JSMethodSignatureCache缓存methodSignature内容,提升方法调用效率

  • 处理返回值,这里逻辑挺多的,核心就是调用Runtime的objc_msgSend方法,对于参数的转换,这里不做讨论。


至此,大体逻辑我们已经理清楚了。里面还有很多细节都写的挺好,还是值得去研究一下。


如果文中有什么错误,欢迎大家指正。

转载请注明出处:http://semyonxu.com

代码注释Demo见:
https://github.com/342261733/JSPatchAnalysis

typeDescription类型的含义可以参考苹果文档:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1

参考JSPatch作者文档:
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源码解析)