JavaScriptCore学习之JSPatch源码阅读

  • 方法调用
    • require实现
    • JS接口
    • 消息传递
    • 对象持有转换
    • 类型转换
    • 示例
  • 方法替换实现
    • 基础原理
    • JPForwardInvocation
  • OC调用

JSPatch的基本原理:JS传递字符串给OC,OC通过Runtime接口调用和替换OC方法。

方法调用

1. require实现

  var _require = function(clsName) {
    if (!global[clsName]) {
      global[clsName] = {
      // _clsName表示这是一个OC的对象
        __clsName: clsName
      }
    } 
    return global[clsName]
  }

  global.require = function(clsNames) {
    var lastRequire
    clsNames.split(',').forEach(function(clsName) {
      lastRequire = _require(clsName.trim())
    })
    return lastRequire
  }

通过这个方法可以看到requre(‘UIView’)这个函数只做了什么,在全局作用域上声明一个对应的变量 UIView,它是类型是一个对象,这个对象有个属性 __clsName 就是’UIView’。

通过clsNames.split(‘,’).foreach这行代码,我们可以看到require一次包含多个对象是如何实现的。

2. JS接口

由于JS不存在OC/ruby那样的消息转发机制,JSPatch作者做了一个很有意思的处理,对JS脚本做个简单编译。将所有JS的方法调用都通过正则进行替换,统一调用 __c() 函数,然后再进行分发。例如下面的转换:

UIView.alloc().init() –> UIView.__c('alloc')().__c('init')()

下面我们可以看一下 __c()需要返回一个function,然后这个function会被调用,下面是__c元函数的源代码

// 为Object的原型定义一个称之为__c的属性,它的值是返回一个function返回值的function,并且设置这个属性configurable:false, enumerable: false。
// JS通过这种为prototype原型添加属性的方式,为所有的JS对象都添加了这个方法。JS对象调用__c方法的时候,通过调用链就可以找到这个方法。
Object.defineProperty(Object.prototype, "__c", {value: function(methodName) {
    // ??
    if (this instanceof Boolean) {
      return function() {
        return false
      }
    }

    // 如果对象的__obj和__clsName都是空,表明这是一个JS对象,在对象中查找并返回对应的方法即可
    if (!this.__obj && !this.__clsName) {
      // 如果在这个JS对象中根本没有这个方法,抛出异常
      if (!this[methodName]) {
        throw new Error(this + '.' + methodName + ' is undefined')
      }
      // 调用bind(this)跟JS的语法特性有关系,绑定返回的这个方法的上下文执行环境为这个对象自身,否则调用的时候,方法内部的this会指向其他离它最近的一个执行环境
      return this[methodName].bind(this);
    }

    // 这里使用self变量保存this的原因是,如果我们直接返回这个function,在里面使用this,指向的就不是当前调用这个函数的对象,所以需要使用self保持this,当然也可以使用bind
    var self = this
    // 在调用super的时候,做了一个特殊处理
    if (methodName == 'super') {
      return function() {
        if (self.__obj) {
          self.__obj.__clsDeclaration = self.__clsDeclaration;
        }
        // 返回一个新的对象,这个对象的__isSuper标识等于1,在OC调用执行的时候,会判断这个标识来决定执行哪一个方法
        return {__obj: self.__obj, __clsName: self.__clsName, __isSuper: 1}
      }
    }

    /** * _methodFunc的作用是把JS的相关调用信息传递给OC,然后OC通过runtime来执行这些调用,返回结果 */

    if (methodName.indexOf('performSelector') > -1) {
      if (methodName == 'performSelector') {
       // return的这个function的作用是:直接调用OC对应的SEL,而不是通过OC调用performSelector,与最下面那个直接调用OC方法的function不同的是关于参数的处理
        return function(){
          var args = Array.prototype.slice.call(arguments)
          return _methodFunc(self.__obj, self.__clsName, args[0], args.splice(1), self.__isSuper, true)
        }
      } else if (methodName == 'performSelectorInOC') {
        // 这个与上面的区别在于,这里的调用是异步,为了防止出现线程竞争问题,调用完毕之后进行callback回调
        // refer: https://github.com/bang590/JSPatch/wiki/performSelectorInOC-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3
        return function(){
          var args = Array.prototype.slice.call(arguments)
          return {__isPerformInOC:1, obj:self.__obj, clsName:self.__clsName, sel: args[0], args: args[1], cb: args[2]}
        }
      }
    }

    // return的这个function的作用是:处理参数,通过_methodFunc进行OC调用,然后返回执行结果
    return function(){
      var args = Array.prototype.slice.call(arguments)
      return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
    }
  }, configurable:false, enumerable: false})

3. 消息传递

JS调用OC函数时,JavaScriptCore会自动会参数和返回值进行转换。基本类型会自动进行转换,详细的转换参考JavaScriptCore的API

通过上面的讲解我们知道,JSPatch主要是通过_methodFunc方法调用OC的消息

  var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
    var selectorName = methodName
    if (!isPerformSelector) {
     // 首先对JS的函数名处理成OC的消息名称
     // 如果OC的方法名中含有"_",在JS中调用的时候需要变成"__",例如OC中_privateMethod,在JS中是__privateMethod()
      methodName = methodName.replace(/__/g, "-")
      // 处理多参数方法名,例 indexPathForRow_inSection --> indexPathForRow:inSection:以及方法名中含有 "_" 做一下特殊处理
      selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_")
      var marchArr = selectorName.match(/:/g)
      var numOfArgs = marchArr ? marchArr.length : 0
      if (args.length > numOfArgs) {
        selectorName += ":"
      }
    }

    // 通过self.__obj变量来判断要调用的方法是instance method还是class method
    // _OC_call / _OC_callC方法是在OC中实现,然后export到JS中的
    var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
                         _OC_callC(clsName, selectorName, args)
    return _formatOCToJS(ret)
  }

关于_OC_call / _OC_callC / _formatOCToJS 后面介绍

4. 对象持有/转换

通过上面的介绍,我们了解到UIView.alloc() 这个类方法调用是如何执行的:
a. require('UIView') 这句话在JS全局变量作用域生成了 UIView 对象,它有个属性叫__isCls,表示这代表一个OC类。
b. 调用UIView这个对象的alloc()方法,会去到__c()函数,在这个函数里判断调用者 __isCls 属性,知道它是代表OC类,把方法名和类名传递给OC完成调用

那问题来了,alloc()返回UIView实例对象给JS,如果直接使用这个对象调用UIView的方法的话,就会抛出异常,因为UIView并不是confirms to JSExport的,所以我们尽管可以在JS中拿到这个对象的指针,并且将这个对象以参数形式出传递给OC,但是却并没有办法操作它的方法或属性,所以这种只是简单wrap的OC对象基本上没法使用。这个问题我在学习JavaScriptCore的时候,一直觉得这个地方严重限制了JavaScriptCore的应用。

在JSPatch中,前面说过,所有的函数调用都会被调换为__c()方法,然后由它进行分发,在这个方法中,我们把OC的对象实例指针以及对应的消息名传递给OC,然后通过runtime去调用实例对应的消息即可。这里的问题变转换为,如何确定这个JS变量是OC对象指针的wrapper?跟前面 UIView 的区分方式类似,这里用的是 __obj 变量来wrap这个对象指针,也就是说当OC对象指针传递给JS之前,转换成一个NSDictionary在发送给JS,而根据JavaScriptCore内置的对象转换规则,NSDictionary会被转为为JS Object。

__c() 方法中的调用代码是:

     return function(){
      var args = Array.prototype.slice.call(arguments)
      // self.__obj指向OC对象指针,self.__clsName是OC的class name。所以如果self.__obj有值,则表明这个JS对象是用来wrap OC对象指针的,这里的调用就变成了调用这个OC实例对象的方法。
      return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
    }

而OC中对应的wrap OC对象指针的代码为:

static NSDictionary *_wrapObj(id obj)
{
    if (!obj || obj == _nilObj) {
        return @{@"__isNil": @(YES)};
    }
    return @{@"__obj": obj};
}

5. 类型转换

JS调用OC的过程,首先传递各个参数给OC,转换JS传来的对象到相应的参数类型,然后调用OC方法,并将返回值包装为对象,然后返回给JS。

6. 示例

思考一下下面的例子,看看其中的问题,就能基本上理解JS与OC的通信过程了

require(‘UIViewController')
var viewController = UIViewController.alloc().init() // UIViewController是如何找到它的alloc()方法的?返回的对象指针,又是如何找到它的init()方法的呢?
var view = viewController.view() // 这里返回一个UIView对象,为什么不需要执行require(‘UIView')?
view.setFrame({x:0, y:0, width:20, height:20}) // view这个OC对象指针的JS wrapper是如何调用它的setFrame方法的?

方法替换/实现

1.基础原理

通过JS替换/新增OC方法,基础原理是OC的method swizzling,具体到JSPatch则是说交换IMP实现,把所有替换或者新增的方法都转发到一个通用的IMP中,JPForwardInvocation。在这个函数中,从它的NSInvocation类型的参数中中取出所有的OC消息的参数。然后把这些参数传递给JS,再调用JS中对应的替换后/新增的方法,最终完成整个调用过程。

有一点需要注意,NSInvocation最少有两个参数, self_cmd"There are always at least 2 arguments, because an NSMethodSignature object includes the hidden arguments self and _cmd, which are the first two arguments passed to every method implementation.",参考:http://stackoverflow.com/questions/5788346/calling-a-selector-with-unknown-number-of-arguments-using-reflection-introspec

具体的交换过程是在overrideMethod函数中实现的,例如上面中的过程,说明一个JSPatch是如何把SEL的调用交换到通用的IMP – JPForwardInvocation上的,我们现在有个SEL名字是XXX,IMP地址为YYY,则在overrideMethod中会额外产生两个新的SEL,同时原始的SEL的IMP也会被修改。我们可以看上图中的过程,原始selector XXX和新产生的selector _JPXXX都会指向IMP – _objc_msgFroward ,而这个C函数的作用,查看头文件可以发现,Use these functions to forward a message as if the receiver did not respond to it.,也就是会触发OC消息转发的过程,也就是到达JSPatch要求的方法 -forwardInvocation:。

JSPatch之所以使用这种方式来转发通过JS实现替换的调用,而不是直接替换为一个通用的IMP,问题在于64位情况下,无法使用va_list获取IMP的参数列表,所以只能通过一种wordaround来解决。把调用在forwardInvocation:的时候进行拦截和转发,因为这个时候可以通过NSInvocation得到这个调用的参数列表和返回值类型。所以在overrideMethod函数中也同样把forwardInvocation:的IMP进行了替换,替换成了通用的IMP指针 JPForwardInvocation。

最后,会把指向对应JS实现JSValue * function以_JPXXX为key缓存到_JSOverideMethods字典中,然后在JPForwardInvocation中取出这个指向JS function的指针进行调用。

还有一点需要注意,对于新增的method,由于无法从JS定义中获取关于方法参数的type encoding,所以对于新增的方法,它的参数类型只能是id,参考:https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%9F%BA%E7%A1%80%E7%94%A8%E6%B3%95#%E6%B7%BB%E5%8A%A0%E6%96%B0%E6%96%B9%E6%B3%95

2. JPForwardInvocation

JPForwardInvocation的功能,首先获取NSInvocation中的所有参数,将消息转发给JS中对应的实现,获取JS调用返回的结果,完整整个的调用过程。

第一段代码的主要是用来初始化

    // 表示这个OC对象是否已经被释放
    BOOL deallocFlag = NO;
    id slf = assignSlf;
    // 初始化获取参数数量
    NSMethodSignature *methodSignature = [invocation methodSignature];
    NSInteger numberOfArguments = [methodSignature numberOfArguments];

    // 转换调用的selector的名字为JSPatch中用来缓存JSValue *function的key的格式
    NSString *selectorName = NSStringFromSelector(invocation.selector);
    NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
    SEL JPSelector = NSSelectorFromString(JPSelectorName);

    // 容错处理,查看是否有对应的JS函数的实现,没有的话,进入OC正常的消息转发流程
    if (!class_respondsToSelector(object_getClass(slf), JPSelector)) {
        JPExcuteORIGForwardInvocation(slf, selector, invocation);
        return;
    }

下面这段代码从OC的NSInvocation中获取调用的参数,这里涉及到OC的type encoding,可以参考Apple的官方guide <Objective-C runtime programming Guide>,参考官方文档:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

    // 初始化数组,用来存储从NSInvocation中获取的参数列表,然后传递给对应的JS函数
    NSMutableArray *argList = [[NSMutableArray alloc] init];
    if ([slf class] == slf) {
        // 如果是类方法,设置__clsName标识表明这是一个OC对象
        [argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
    } else if ([selectorName isEqualToString:@"dealloc"]) {
        // 针对要被释放的对象,使用assign来保存self指针
        [argList addObject:[JPBoxing boxAssignObj:slf]];
        deallocFlag = YES;
    } else {
        // 使用weak来保存self指针
        [argList addObject:[JPBoxing boxWeakObj:slf]];
    }

    // NSInvocation的前两个参数分别为self和_cmd,所以直接从第3个参数开始获取
    for (NSUInteger i = 2; i < numberOfArguments; i++) {
        const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
        // 判断返回值是否为const,如果const,获取后面的encoding来判断类型
        switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {

            #define JP_FWD_ARG_CASE(_typeChar, _type) \
            case _typeChar: {   \
                _type arg;  \
                // 从invocation中获取参数,并将参数添加到argList数组中
                [invocation getArgument:&arg atIndex:i];    \
                [argList addObject:@(arg)]; \
                break;  \
            }
            JP_FWD_ARG_CASE('c', char)
            JP_FWD_ARG_CASE('C', unsigned char)
            JP_FWD_ARG_CASE('s', short)
            JP_FWD_ARG_CASE('S', unsigned short)
            JP_FWD_ARG_CASE('i', int)
            JP_FWD_ARG_CASE('I', unsigned int)
            JP_FWD_ARG_CASE('l', long)
            JP_FWD_ARG_CASE('L', unsigned long)
            JP_FWD_ARG_CASE('q', long long)
            JP_FWD_ARG_CASE('Q', unsigned long long)
            JP_FWD_ARG_CASE('f', float)
            JP_FWD_ARG_CASE('d', double)
            JP_FWD_ARG_CASE('B', BOOL)
            case '@': {
                // 处理id类型的参数,使用__unsafe_unretianed参数,避免内存泄露???
                __unsafe_unretained id arg;
                [invocation getArgument:&arg atIndex:i];
                // 对于block参数做特殊处理,需要进行copy;使用_nilObj表示nil,让参数可以完整传递给JS
                if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {
                    [argList addObject:(arg ? [arg copy]: _nilObj)];
                } else {
                    [argList addObject:(arg ? arg: _nilObj)];
                }
                break;
            }
            case '{': {
                // 处理结构体类型参数
                // 获取结构体类型的名称,然后根据这个把参数包装为JSValue类型
                NSString *typeString = extractStructName([NSString stringWithUTF8String:argumentType]);
                #define JP_FWD_ARG_STRUCT(_type, _transFunc) \
                if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
                    _type arg; \
                    [invocation getArgument:&arg atIndex:i];    \
                    [argList addObject:[JSValue _transFunc:arg inContext:_context]];  \
                    break; \
                }
                JP_FWD_ARG_STRUCT(CGRect, valueWithRect)
                JP_FWD_ARG_STRUCT(CGPoint, valueWithPoint)
                JP_FWD_ARG_STRUCT(CGSize, valueWithSize)
                JP_FWD_ARG_STRUCT(NSRange, valueWithRange)

                // 处理自定义类型的结构体
                @synchronized (_context) {
                    NSDictionary *structDefine = _registeredStruct[typeString];
                    if (structDefine) {
                        size_t size = sizeOfStructTypes(structDefine[@"types"]);
                        if (size) {
                            void *ret = malloc(size);
                            [invocation getArgument:ret atIndex:i];
                            NSDictionary *dict = getDictOfStruct(ret, structDefine);
                            [argList addObject:[JSValue valueWithObject:dict inContext:_context]];
                            free(ret);
                            break;
                        }
                    }
                }

                break;
            }
            case ':': {
               // 处理selector类型参数
                SEL selector;
                [invocation getArgument:&selector atIndex:i];
                NSString *selectorName = NSStringFromSelector(selector);
                [argList addObject:(selectorName ? selectorName: _nilObj)];
                break;
            }
            case '^':
            case '*': {
               // 处理指针类型
                void *arg;
                [invocation getArgument:&arg atIndex:i];
                [argList addObject:[JPBoxing boxPointer:arg]];
                break;
            }
            case '#': {
                // Class类类型
                Class arg;
                [invocation getArgument:&arg atIndex:i];
                [argList addObject:[JPBoxing boxClass:arg]];
                break;
            }
            default: {
                NSLog(@"error type %s", argumentType);
                break;
            }
        }
    }

下面这段代码根据上面获取的参数列表,执行对应的JS方法

// 将OC的对象转换为对应的JS类型
static id formatOCToJS(id obj)
{
   // 对于这四种类型的数据,其中的NSArray/NSString/NSDictionary类型,JavaScriptCore是支持JS与OC之间的转换的,但是JSPatch对他们做了特殊的处理,所以在JS中下面这些数据的使用跟普通的NSObject一样,如果要使用对应的JS类型,使用toJS()接口转换一下即可。参考:https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%9F%BA%E7%A1%80%E7%94%A8%E6%B3%95#5-nsarray--nsstring--nsdictionary
    if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSDictionary class]] || [obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDate class]]) {
        return _wrapObj([JPBoxing boxObj:obj]);
    }
    // 对于NSNumber/NSBlock/JSValue则直接传递这个对象给JS即可
    if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:NSClassFromString(@"NSBlock")] || [obj isKindOfClass:[JSValue class]]) {
        return obj;
    }
    // 否则,对于不在特殊处理范围之内的对象,转换为NSDictioanry的封装 {"__obj":obj} ,用于在JS中识别这是一个OC对象
    return _wrapObj(obj);
}
    // _formatOCToJSList通过调用formatOCToJS将参数数组转为对应的JS对象数组
    NSArray *params = _formatOCToJSList(argList);
    // 获取这个method返回值的类型
    const char *returnType = [methodSignature methodReturnType];

    // 判断返回值是否为const,如果const,获取后面的encoding来判断类型
    switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
        #define JP_FWD_RET_CALL_JS \
            // 通过JPSelectorName,获取在overrideMethod()函数中保存的指向对应JS实现的函数指针
            JSValue *fun = getJSFunctionInObjectHierachy(slf, JPSelectorName); \
            JSValue *jsval; \
            [_JSMethodForwardCallLock lock];   \
            // 通过JSValue *function这个JS函数指针,调用对应的JS实现,同时传入之前处理得到的参数列表params
            jsval = [fun callWithArguments:params]; \
            [_JSMethodForwardCallLock unlock]; \
            // 如果这个JS函数是特殊的调用,即performSelectorInOC,则直接通过callSelector函数在OC中直接调用处理,参考:https://github.com/bang590/JSPatch/wiki/performSelectorInOC-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3
            while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) { \
                NSArray *args = nil;  \
                JSValue *cb = jsval[@"cb"]; \
                if ([jsval hasProperty:@"sel"]) {   \
                    // 直接通过OC调用这个方法
                    id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO);  \
                    // 处理调用返回结果,将返回的OC结果处理为JS调用适合的参数类型
                    args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]];  \
                }   \
                [_JSMethodForwardCallLock lock];    \
                // 使用之前处理过的参数,执行JS回调
                jsval = [cb callWithArguments:args];  \
                [_JSMethodForwardCallLock unlock];  \
            }

        // 根据返回结果的encoding,来设置返回结果的return value
        #define JP_FWD_RET_CASE_RET(_typeChar, _type, _retCode) \
            case _typeChar : { \
                JP_FWD_RET_CALL_JS \
                _retCode \
                [invocation setReturnValue:&ret];\
                break;  \
            }

        #define JP_FWD_RET_CASE(_typeChar, _type, _typeSelector) \
            JP_FWD_RET_CASE_RET(_typeChar, _type, _type ret = [[jsval toObject] _typeSelector];)   \

        #define JP_FWD_RET_CODE_ID \
            id __autoreleasing ret = formatJSToOC(jsval); \
            if (ret == _nilObj ||   \
                ([ret isKindOfClass:[NSNumber class]] && strcmp([ret objCType], "c") == 0 && ![ret boolValue])) ret = nil;  \

        #define JP_FWD_RET_CODE_POINTER \
            void *ret; \
            id obj = formatJSToOC(jsval); \
            if ([obj isKindOfClass:[JPBoxing class]]) { \
                ret = [((JPBoxing *)obj) unboxPointer]; \
            }

        #define JP_FWD_RET_CODE_CLASS \
            Class ret;   \
            id obj = formatJSToOC(jsval); \
            if ([obj isKindOfClass:[JPBoxing class]]) { \
                ret = [((JPBoxing *)obj) unboxClass]; \
            }

        #define JP_FWD_RET_CODE_SEL \
            SEL ret;   \
            id obj = formatJSToOC(jsval); \
            if ([obj isKindOfClass:[NSString class]]) { \
                ret = NSSelectorFromString(obj); \
            }

        JP_FWD_RET_CASE_RET('@', id, JP_FWD_RET_CODE_ID)
        JP_FWD_RET_CASE_RET('^', void*, JP_FWD_RET_CODE_POINTER)
        JP_FWD_RET_CASE_RET('*', void*, JP_FWD_RET_CODE_POINTER)
        JP_FWD_RET_CASE_RET('#', Class, JP_FWD_RET_CODE_CLASS)
        JP_FWD_RET_CASE_RET(':', SEL, JP_FWD_RET_CODE_SEL)

        JP_FWD_RET_CASE('c', char, charValue)
        JP_FWD_RET_CASE('C', unsigned char, unsignedCharValue)
        JP_FWD_RET_CASE('s', short, shortValue)
        JP_FWD_RET_CASE('S', unsigned short, unsignedShortValue)
        JP_FWD_RET_CASE('i', int, intValue)
        JP_FWD_RET_CASE('I', unsigned int, unsignedIntValue)
        JP_FWD_RET_CASE('l', long, longValue)
        JP_FWD_RET_CASE('L', unsigned long, unsignedLongValue)
        JP_FWD_RET_CASE('q', long long, longLongValue)
        JP_FWD_RET_CASE('Q', unsigned long long, unsignedLongLongValue)
        JP_FWD_RET_CASE('f', float, floatValue)
        JP_FWD_RET_CASE('d', double, doubleValue)
        JP_FWD_RET_CASE('B', BOOL, boolValue)

        case 'v': {
            JP_FWD_RET_CALL_JS
            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;
        }
    }

如果被JS替换的方法是dealloc,则需要特殊处理一下

    // 如果hook的是dealloc方法,则不能通过调用ORIGXXX()的方式来调用原dealloc方法
    if (deallocFlag) {
        slf = nil;
        Class instClass = object_getClass(assignSlf);
        Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc"));
        // 获取原dealloc的IMP指针,然后直接调用dealloc,防止产生内存泄露
        void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
        originalDealloc(assignSlf, NSSelectorFromString(@"dealloc"));
    }

OC调用

在JSPatch中,JS调用OC的方法,是通过 callSelector 实现的,通过传入的class name / instance 指针 / selector name / 参数列表,即可以做到直接调用OC的selector。这个跟OC的动态特性有关系,对一个消息的发送可以很灵活控制,这也是JSPatch能实现的技术基础。这也是为什么纯粹swift class不能够使用JSPatch的原因。

至于这部分的具体实现,由于这里调用的selector都是OC对象现有的方法,所以很容易拿到这个方法的签名,进而获得NSInvocation,然后根据type encoding,设置NSInvocation的参数。如果对OC的runtime比较熟悉的话,还是比较容易理解的,这里不多写了。

你可能感兴趣的:(JavaScriptCore学习之JSPatch源码阅读)