JavaScriptCore学习之JSPatch源码阅读
2016-04-04 23:48
585 查看
方法调用
require实现
JS接口
消息传递
对象持有转换
类型转换
示例
方法替换实现
基础原理
JPForwardInvocation
OC调用
JSPatch的基本原理:
通过这个方法可以看到requre(‘UIView’)这个函数只做了什么,在全局作用域上声明一个对应的变量 UIView,它是类型是一个对象,这个对象有个属性
通过clsNames.split(‘,’).foreach这行代码,我们可以看到require一次包含多个对象是如何实现的。
下面我们可以看一下
通过上面的讲解我们知道,JSPatch主要是通过
关于
那问题来了,alloc()返回UIView实例对象给JS,如果直接使用这个对象调用UIView的方法的话,就会抛出异常,因为UIView并不是confirms to JSExport的,所以我们尽管可以在JS中拿到这个对象的指针,并且将这个对象以参数形式出传递给OC,但是却并没有办法操作它的方法或属性,所以这种只是简单wrap的OC对象基本上没法使用。这个问题我在学习JavaScriptCore的时候,一直觉得这个地方严重限制了JavaScriptCore的应用。
在JSPatch中,前面说过,所有的函数调用都会被调换为
而OC中对应的wrap OC对象指针的代码为:
有一点需要注意,NSInvocation最少有两个参数,
![](https://img-blog.csdn.net/20160404234726936)
具体的交换过程是在overrideMethod函数中实现的,例如上面中的过程,说明一个JSPatch是如何把SEL的调用交换到通用的IMP – JPForwardInvocation上的,我们现在有个SEL名字是XXX,IMP地址为YYY,则在overrideMethod中会额外产生两个新的SEL,同时原始的SEL的IMP也会被修改。我们可以看上图中的过程,原始selector
JSPatch之所以使用这种方式来转发通过JS实现替换的调用,而不是直接替换为一个通用的IMP,问题在于64位情况下,无法使用
最后,会把指向对应JS实现JSValue * 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
第一段代码的主要是用来初始化
下面这段代码从OC的NSInvocation中获取调用的参数,这里涉及到OC的type encoding,可以参考Apple的官方guide
下面这段代码根据上面获取的参数列表,执行对应的JS方法
如果被JS替换的方法是dealloc,则需要特殊处理一下
至于这部分的具体实现,由于这里调用的selector都是OC对象现有的方法,所以很容易拿到这个方法的签名,进而获得NSInvocation,然后根据type encoding,设置NSInvocation的参数。如果对OC的runtime比较熟悉的话,还是比较容易理解的,这里不多写了。
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比较熟悉的话,还是比较容易理解的,这里不多写了。
相关文章推荐
- 《JavaScript高级程序设计》——JS中其他对象与常用方法
- HTML中使用JavaScript
- js, 树状菜单隐藏显示
- javascript控制input只允许输入数字
- javascript控制input只允许输入数字
- javascript控制input只允许输入数字
- 每周小节-2 d3-zoom,pan,brush阅读心得 & d3.js on angular读书笔记
- JavaScript创建对象
- JavaScript-数据类型-类型检测
- [BZOJ1030][JSOI2007]文本生成器(AC自动机+dp)
- 基于JSP在线答题程序设计
- javascript面向对象之闭包
- js配合锚点实现动画滚动与监听
- JavaScript之数据类型
- JavaScript反选-全选-全不选代码
- Newtonsoft.Json 自定义序列化特性
- JavaScript进阶基础一
- [练习4]js学习之图片的水平滚动
- js 事件捕获与事件冒泡例子
- JSON