您的位置:首页 > Web前端 > JavaScript

JavaScriptCore学习之JSPatch源码阅读

2016-04-04 23:48 585 查看
方法调用
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比较熟悉的话,还是比较容易理解的,这里不多写了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: