JSPatch源码解读

原理

JSPatch 能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言,OC 上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法。

执行过程

当客户端从服务器下载一段JS代码后: 1.客户端调用[JPEngine StartEngine],这时会创建一个JSContext *_context,然后给_context创造各种方法:

context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {
return callSelector(nil, selectorName, arguments, obj, isSuper);
};
context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) {
return callSelector(className, selectorName, arguments, nil, NO);
};
context[@"_OC_formatJSToOC"] = ^id(JSValue *obj) {
return formatJSToOC(obj);
};
context[@"_OC_formatOCToJS"] = ^id(JSValue *obj) {
return formatOCToJS([obj toObject]);
};

......
复制代码

这些方法会通过JavaScriptCore暴露给JS进行调用。 2.进行完_context初始化后,会通过_context执行JSPatch自带的JS语句,进行JS环境下的全局变量的初始化,比如将JS中方法,参数转成OC格式语句的核心方法:

//_methodFunc是JS调用OC的入口函数
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
//判断是否是直接调用performSelector,若不是则进行方法名加工
if (!isPerformSelector) {
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执行后的返回值转化为JS格式
return _formatOCToJS(ret)
}
复制代码

还有JS中所有调用方法的元函数__c:

__c: function(methodName) {
var slf = this

if (slf instanceof Boolean) {
return function() {
return false
}
}
if (slf[methodName]) {
//将从prototype获取的函数指定绑定作用域
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);
}
var clsName = slf.__clsName
//假如是下发JS中存在的方法,直接返回JS中的方法
if (clsName && _ocCls[clsName]) {
var methodType = slf.__obj ? 'instMethods': 'clsMethods'
if (_ocCls[clsName][methodType][methodName]) {
slf.__isSuper = 0;
return _ocCls[clsName][methodType][methodName].bind(slf)
}
}
//否则返回一个匿名函数,匿名函数的返回值作为最终的返回值进行接下来的JS方法调用
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
}
}
复制代码

还有其他的全局变量声明,只把这两个最核心的函数进行展示,就不一一赘述了。

3.进行完所有前期工作后,接下来就是进行下发JS语句的执行,首先利用正则表达式进行JS中的方法替换,比如var tableViewCtrl = JPTableViewController.alloc().init()会转化成 var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")()

4.接下来就会通过[_context evaluateScript:script withSourceURL:[NSURL URLWithString:@"JSPatch.js"]];执行替换后的代码。 调用global.defineClass = function(declaration, properties, instMethods, clsMethods) {} 这个函数内部会判断properties是不是数组

if (!(properties instanceof Array)) {
clsMethods = instMethods
instMethods = properties
properties = null
}
复制代码

然后调用_formatDefineMethods(instMethods, newInstMethods, realClsName),这个函数主要是构建之前声明的newInstMethodsnewClsMethods:

newMethods : {
method1: [function(param1,param2...).length ,function(){}],
method2: [function(param1,param2...).length ,function(){}]
}
复制代码

数组内的function是匿名函数(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)
//调用原始的JS方法
var ret = originMethod.apply(originMethod, args)
global.self = lastSelf
return ret
} catch(e) {
_OC_catch(e.message, e.stack)
}
}
复制代码

然后通过_OC_defineClassdeclarationnewInstMethodsnewClsMethods暴露给OC。

var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
复制代码

最终在OC中调用defineClass,将JS中传过来的classDeclaration进行解析,得到当前类和父类以及协议。 当没有找到当前类时,就在父类中注册一个新当前类。给当前类添加完协议,将覆盖或者新建的方法通过runtime将imp指针统一替换成JPForwardInvocation的函数指针。 当在OC中调用JS方法会通过JPForwardInvocation,给这个匿名函数传入OC的参数,获取原始JS函数的返回值。

if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
//class_replaceMethod这个方法交换不存在的方法时会等同于addMethod,返回nil
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
if (originalForwardImp) {
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
}
}
复制代码

利用runtime进行方法添加或交换imp然后调用overrideMethod,将上述的匿名函数(1)保存到_JSOverideMethods。 当defineClass在OC环境中执行完毕后,会将Class和SuperClass返回到JS环境下,为了在JS环境中模仿子类能都调用父类的方法,会将父类中被JS重写的方法会同样保存在子类中。而所有重写类的所有类方法和实例方法都放在_ocCls中,大致结构是

_ocCls : {
className: {
instMethods: {
//这里的function就是下发的脚本中对应的方法对应的js函数
methodName: function(){},
methodName1: function(){}
},
clsMethods: {
methodName: function(){},
methodName1: function(){}
},
},
className1: {
instMethods: {
methodName: function(){},
methodName1: function(){}
},
clsMethods: {
methodName: function(){},
methodName1: function(){}
},
}
}
复制代码

在JS中调用元函数时,会判断_ocCls中是否存在对应的方法,若有,就调用_ocCls[clsName][methodType][methodName].bind(slf),将调用者slf通过bind传到对应函数中,等待__c调用。

到此,在执行完下载的脚本后,所需覆盖的OC方法的imp指针已经指向了JPForwardInvocation

static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
{
//精简前面部分代码
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
//1.替换当前类的forwardInvocation为JPForwardInvocation
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
if (originalForwardImp) {
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
}

}

[cls jp_fixMethodSignature];
if (class_respondsToSelector(cls, selector)) {
//2.假如selector有实现,则添加一个新的Selector指向原始的imp,这时候原始的seletor也是指向原始的imp
NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName];
SEL originalSelector = NSSelectorFromString(originalSelectorName);
if(!class_respondsToSelector(cls, originalSelector)) {
class_addMethod(cls, originalSelector, originalImp, typeDescription);
}
}

NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];

_initJPOverideMethods(cls);
_JSOverideMethods[cls][JPSelectorName] = function;

//3.在最后才进行原始selector的imp替换,防止有可能的调用overrideMethod时同时调用这个方法导致的多线程问题。
//再将原始的Selector指向原始的_objc_msgForward,这样只要在外界调用这个selector直接跳过imp查找,
//先后执行resolveInstanceMethod,forwardingTargetForSelector,methodSignatureForSelector,forwardInvocation
//当在调用forwardInvocation就会跳到JPForwardInvocation
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);

}
复制代码

到此,所有前期配置都已完成,接下来就是等待覆写的方法被调用时,触发JPForwardInvocation,进行OC和JS的来回传值。

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];
//在_JSOverideMethods中获取之前保存的JSFunc,用于调用原始的JS函数
JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);
if (!jsFunc) {
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}
//下面是将参数传入到JS的代码
//将调用者信息保存到argList
NSMutableArray *argList = [[NSMutableArray alloc] init];
if ([slf class] == slf) {
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) {
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else {
[argList addObject:[JPBoxing boxWeakObj:slf]];
}
//methodSignatured的前两个参数固定是调用者和seletor
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
//将参数依次取出,判断类型,添加到arglist
case '@': {
__unsafe_unretained id arg;
[invocation getArgument:&arg atIndex:i];
if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {
[argList addObject:(arg ? [arg copy]: _nilObj)];
} else {
[argList addObject:(arg ? arg: _nilObj)];
}
break;
}
default: {
NSLog(@"error type %s", argumentType);
break;
}
}
}

//将普通对象数组转换为字典对象数组,里面有参数的实例和类名 @[{__obj:obj,__cls:NSObject},{__obj:obj2,__cls:NSObject}]
NSArray *params = _formatOCToJSList(argList);
//下面是处理返回值类型传入到JS
char returnType[255];
strcpy(returnType, [methodSignature methodReturnType]);

// Restore the return type
if (strcmp(returnType, @encode(JPDouble)) == 0) {
strcpy(returnType, @encode(double));
}
if (strcmp(returnType, @encode(JPFloat)) == 0) {
strcpy(returnType, @encode(float));
}
//当调用实例方法,且参数是id类型时调用顺序: jsFunc->originJSMethod->__c->_methodFunc->_OC_callI->callSelector->获取返回值,一步步回传到jsVal
//如此就完成了JS和OC的通信,整个过程是串行执行的。

switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
#define JP_FWD_RET_CALL_JS \
JSValue *jsval; \
[_JSMethodForwardCallLock lock];   \
jsval = [jsFunc callWithArguments:params]; \
[_JSMethodForwardCallLock unlock]; \
case 'v': {
JP_FWD_RET_CALL_JS
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"));
}
}
复制代码

关于block传递

当JSPatch扫描下发脚本时,会将所有的block标记,还是以调用实例方法为例,当调用链走到callSelector时,对JS参数进行解析的时候,假如发现有block

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];
Block_release((__bridge void *)cb);
} else {
__autoreleasing id cb = genCallbackBlock(arguments[i-2]);
[invocation setArgument:&cb atIndex:i];
}
}
复制代码

将传入的block通过libffi生成一个函数指针,替换invocation原来的参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值