前言
JSPatch是一个非常棒的热修复框架,10000+star!!!!!虽说2017年被苹果封杀了,但是据我获取到的有限的信息,大家还是偷偷摸摸混淆一下、改改类名继续在使用。毕竟bug还是不可避免的,有了JSPatch万一出了问题我们还是能够抢救一下的。一些实现细节bang哥其实已经做了一些介绍了,但是看了之后还是不能对完整的流程有个大致的印象,并且其中肯定还是有很多东西值得我们去深挖学习的。
JSPatch简介
JSPatch 是一个 iOS 动态更新框架,只需在项目中引入极小的引擎,就可以使用 JavaScript
调用任何 Objective-C
原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug。
大致需要了解的
JavaScript
- 还是要稍微会一点点js的,我就是那种菜的抠脚的水平,所以js部分看的有点吃力。不懂的地方都是靠调试js代码打断点,根据结果反向推来搞懂的。- JSPatch里
JavaScript
的写法。还是那句话,用过之后会有一些问题,带着问题去读源码更有针对性,效果更好。 JavaScriptCore
- js和oc内部交互用的是JavaScriptCore
,推荐阅读JavaScriptCore 整体介绍。Runtime
- JSPatch内部是通过Runtime来动态修改添加方法的,涉及到了很多很多的runtime的知识。所以你还是需要了解一些runtime的知识。推荐阅读Objective-C Runtime。
带着问题看源码
国际惯例,抛砖引玉提俩问题。
1. 添加方法的时候,方法的编码是怎么做处理的
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
复制代码
我知道runtime里添加一个方法里会需要我们传递一个方法的编码。苹果的运行时库内部利用类型编码帮助加快消息分发。方法的编码主要描述了方法的返回值和参数。比如这样一个方法-(void)test:(id)arg;
最后拿到的编码是v@:@
,具体各个字符对应关系Type Encodings。但是你可能会有个问题这里算上返回值一共就俩个参数,但是这里一共有四个字符。原因是[receiver test:arg]
会被编译器转化成objc_msgSend(receiver, selector, arg1)
我们拿到的签名里的参数部分被加上了一个self
和一个SEL
。
JS里的方法显然是没有这样的编码的,那么从JS到OC方法的编码是如何处理的呢。
2. JS function内是怎么调用OC方法的
如果某个JS对象不存在某个方法那么调用是要出错的。例如
UIView.alloc()
如果UIView
对象没有alloc
这个方法调用是会出问题的。
粗略的看一下流程
简化了很多细节,demo里的代码的大致的流程如下。用两种颜色区分了js端和native端。
从这个流程图当中我们大致可以了解到,JSPatch最后的方法调用和Aspects
库类似都是走的消息转发。而且native方法不存在的时候JSPatch并不会去动态生成一个方法。所以当Aspects
和JSPatch
混用的时候需要特别小心。
回头看疑问
问题1
关于第一个方法编码的问题,JSPatch分了几种情况去处理
- 协议里的方法
- native已经实现了的方法
- 默认的情况
对应的大致的代码逻辑,可以看到如果是添加了一个之前完全不存在的方法,是需要知道有多少个参数来自己创建一个方法编码的。所以之前的js里有一步是要生成一个[方法参数个数,方法实现]
这样的数据结构。
// 如果已经实现了这个方法
if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {
// overrideMethod方法内部通过下面的逻辑来获取编码
// Method method = class_getInstanceMethod(cls, selector);
// typeDescription = (char *)method_getTypeEncoding(method);
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]);
}
}
}
复制代码
问题2
关于这个问题bang哥已经有一些介绍,通过正则,类似UIView.alloc().init()
的调用被替换成UIView.__c('alloc')().__c('init')()
形式。走统一的__c()
元函数,传递进方法名拿到一个方法,再传递进参数调用方法。
实现细节
JSPatch细节有点多。我这边只挑一些主要的看,默认你已经大致看过源码。
从demo的执行入口开始
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
复制代码
startEngine
相当于初始化运行环境,它的内部通过JavaScriptCore
定义了很多JS的方法,给外部的js来和native交互。
+ (void)startEngine
{
.
.
.
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) {
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]);
};
.
.
.
复制代码
js可以直接调用这些方法并且传递参数,传递的参数在这里会自动转换。具体的转换对应关系。
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
复制代码
上面是环境的初始化,初始化完就是js脚本的调用。[JPEngine evaluateScript:script];
evaluateScript
方法的主要作用就是把原始的js方法里方法调用正则修改成__c
函数调用。也就是上面问题2的描述。
+ (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];
}
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];
} else {
return [_context evaluateScript:formatedScript];
}
}
@catch (NSException *exception) {
_exceptionBlock([NSString stringWithFormat:@"%@", exception]);
}
return nil;
}
复制代码
接下来就是JavaScriptCore
执行js脚本了。
defineClass('JPViewController', {
viewDidAppear:function(animate) {
console.log('lol');
},
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)
}
})
复制代码
global.defineClass = function(declaration, properties, instMethods, clsMethods) {
var newInstMethods = {}, newClsMethods = {}
if (!(properties instanceof Array)) {
clsMethods = instMethods
instMethods = properties
properties = null
}
// 如果有属性的情况下,需要去判断有没有实现set get方法,如果实现了就要加到实例方法列表里面去
if (properties) {
properties.forEach(function(name){
if (!instMethods[name]) {
instMethods[name] = _propertiesGetFun(name);
}
var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1);
if (!instMethods[nameOfSet]) {
instMethods[nameOfSet] = _propertiesSetFun(name);
}
});
}
// 解析出类名
var realClsName = declaration.split(':')[0].trim()
_formatDefineMethods(instMethods, newInstMethods, realClsName)
_formatDefineMethods(clsMethods, newClsMethods, realClsName)
var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
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]
}
}
_setupJSMethod(className, instMethods, 1, realClsName)
_setupJSMethod(className, clsMethods, 0, realClsName)
return require(className)
}
复制代码
我们看到defineClass
方法内部做了一些解析处理,并且调用了我们之前注册的_OC_defineClass
方法,传递进去了类名、实例方法、类方法。OC部分的defineClass
方法如下
static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];
NSString *className;
NSString *superClassName;
NSString *protocolNames;
// xxObject : xxSuperObject <xxProtocol>
// 解析类
[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];
}
}
// 没有父类信息的情况就继承自NSObject
if (!superClassName) superClassName = @"NSObject";
// 删除前后空格
className = trim(className);
superClassName = trim(superClassName);
// 解析出协议
NSArray *protocols = [protocolNames length] ? [protocolNames componentsSeparatedByString:@","] : nil;
Class cls = NSClassFromString(className);
if (!cls) {// 如果当前的类不存在
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);
}
}
// 实例方法和类方法处理 1是实例方法 2是类方法
for (int i = 0; i < 2; i ++) {
BOOL isInstance = i == 0;
// 传递过来的方法都是 `方法名` : `[方法参数个数,方法实现]` 这样的形式
JSValue *jsMethods = isInstance ? instanceMethods: classMethods;
Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);
NSDictionary *methodDict = [jsMethods toDictionary];
for (NSString *jsMethodName in methodDict.allKeys) {
JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
// 参数的个数
int numberOfArg = [jsMethodArr[0] toInt32];
// JS的方法名字转换成OC的方法名字
NSString *selectorName = convertJPSelectorString(jsMethodName);
// 为了js端的写法好看点,末尾的参数可以不用写 `_`
if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {
selectorName = [selectorName stringByAppendingString:@":"];
}
JSValue *jsMethod = jsMethodArr[1];
// 如果已经实现了这个方法
if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {
// overrideMethod方法内部通过下面的逻辑来获取编码
// Method method = class_getInstanceMethod(cls, selector);
// typeDescription = (char *)method_getTypeEncoding(method);
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]);
}
}
}
}
}
class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
return @{@"cls": className, @"superCls": superClassName};
}
复制代码
我们主要来看一下针对方法部分的操作,可以看到区分不同的情况拿到方法的编码之后走的都是overrideMethod
方法。再来看看这个方法的主要逻辑。
static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
{
SEL selector = NSSelectorFromString(selectorName);
// 拿到方法签名
if (!typeDescription) {
Method method = class_getInstanceMethod(cls, selector);
typeDescription = (char *)method_getTypeEncoding(method);
}
// 拿到原始方法的IMP如果方法实现了的话
IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;
// 拿到消息转发指针
IMP msgForwardIMP = _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
// 替换消息转发 `forwardInvocation:`的实现
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
if (originalForwardImp) {
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");// 存一下原始的IMP
}
}
// invocation return 0 when the return type is double/float.
[cls jp_fixMethodSignature];
// 如果实现了这个方法,做一下区分生成一个新的 ORIGSelector -> 原始方法的IMP。 为了JS能调用到之前的方法
if (class_respondsToSelector(cls, selector)) {
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;
// 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);
}
复制代码
这个方法和Aspects
里的核心方法很像。主要的操作就是替换forwardInvocation
的实现,替换要调用的方法的SEL指向_objc_msgForward
。也就是方法调用的时候直接走消息转发,执行JPForwardInvocation
方法。并且也把传递过来的JS方法存了一下,以便在JPForwardInvocation
方法里调用。JPForwardInvocation
的方法实在是有点长,它主要的流程如下图
代码如下,有删除一些内容。
static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
{
// 调用栈
#ifdef DEBUG
_JSLastCallStack = [NSThread callStackSymbols];
#endif
BOOL deallocFlag = NO;
id slf = assignSlf;
// 方法签名
NSMethodSignature *methodSignature = [invocation methodSignature];
// 方法参数
NSInteger numberOfArguments = [methodSignature numberOfArguments];
// 方法名字
NSString *selectorName = NSStringFromSelector(invocation.selector);
// JS方法名字
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
// 拿到js的function
JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);
if (!jsFunc) {
// 如果没有实现JS的方法就调用原始的消息转发
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}
// JPBoxing的作用是防止强制转换
NSMutableArray *argList = [[NSMutableArray alloc] init];
if ([slf class] == slf) {
// 类方法
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) {
// dealloc方法添加一个assign的self对象
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else {
// 默认的情况添加一个weak的self对象
[argList addObject:[JPBoxing boxWeakObj:slf]];
}
// 方法签名的 0是self 1是_cmd
// 遍历获取方法的参数,
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
// 基础数据类型
#define JP_FWD_ARG_CASE(_typeChar, _type) \
case _typeChar: { \
_type arg; \
[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 '@': {
__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;
}
// struct
case '{': {
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 ':': {
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 arg;
[invocation getArgument:&arg atIndex:i];
[argList addObject:[JPBoxing boxClass:arg]];
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;
}
}
// 把oc的参数转成js的参数
NSArray *params = _formatOCToJSList(argList);
// 返回数据的encode
char returnType[255];
strcpy(returnType, [methodSignature methodReturnType]);
// 处理7.1系统下返回double float 0的问题
// Restore the return type
if (strcmp(returnType, @encode(JPDouble)) == 0) {
strcpy(returnType, @encode(double));
}
if (strcmp(returnType, @encode(JPFloat)) == 0) {
strcpy(returnType, @encode(float));
}
// 拿到jsFunc 直接调用 `[jsFunc callWithArguments:params]` 然后根据returnType转成对应的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]; \
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]; \
}
#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; \
ret = formatJSToOC(jsval);
#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的方法然后进行的一些操作。
下面再来看一下js方法内部是如何调用原生的方法的。JS部分的代码就不贴全了,主要的操作逻辑是给JS 对象基类对象加加上__c
成员,这样所有对象都可以调用到__c
, __c
内部还有一些判断是否是对象、是否调用的是父类的方法等等的操作,最后调用的是_methodFunc方法。
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
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)
return _formatOCToJS(ret)
}
复制代码
然后我们看到,方法内部做了一些判断和解析。最后对象方法调用的是在native里注册了的_OC_callI
类方法调用的是在native里注册了的_OC_callC
方法。这两个方法都走的是callSelector
方法。这个方法也是老长。它的主要流程图如下。
代码如下
static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper)
{
NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString];
if (instance) {
// JSValue -> OC对象
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);
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<NSCopying>)cls] = [[NSMutableDictionary alloc]init];
}
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);
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);
return formatOCToJS(result);
}
// 设置方法参数
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
id valObj = argumentsObj[i-2];
switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
#define JP_CALL_ARG_CASE(_typeString, _type, _selector) \
case _typeString: { \
_type value = [valObj _selector]; \
[invocation setArgument:&value atIndex:i];\
break; \
}
JP_CALL_ARG_CASE('c', char, charValue)
JP_CALL_ARG_CASE('C', unsigned char, unsignedCharValue)
JP_CALL_ARG_CASE('s', short, shortValue)
JP_CALL_ARG_CASE('S', unsigned short, unsignedShortValue)
JP_CALL_ARG_CASE('i', int, intValue)
JP_CALL_ARG_CASE('I', unsigned int, unsignedIntValue)
JP_CALL_ARG_CASE('l', long, longValue)
JP_CALL_ARG_CASE('L', unsigned long, unsignedLongValue)
JP_CALL_ARG_CASE('q', long long, longLongValue)
JP_CALL_ARG_CASE('Q', unsigned long long, unsignedLongLongValue)
JP_CALL_ARG_CASE('f', float, floatValue)
JP_CALL_ARG_CASE('d', double, doubleValue)
JP_CALL_ARG_CASE('B', BOOL, boolValue)
case ':': {
SEL value = nil;
if (valObj != _nilObj) {
value = NSSelectorFromString(valObj);
}
[invocation setArgument:&value atIndex:i];
break;
}
case '{': {
NSString *typeString = extractStructName([NSString stringWithUTF8String:argumentType]);
JSValue *val = arguments[i-2];
#define JP_CALL_ARG_STRUCT(_type, _methodName) \
if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
_type value = [val _methodName]; \
[invocation setArgument:&value atIndex:i]; \
break; \
}
JP_CALL_ARG_STRUCT(CGRect, toRect)
JP_CALL_ARG_STRUCT(CGPoint, toPoint)
JP_CALL_ARG_STRUCT(CGSize, toSize)
JP_CALL_ARG_STRUCT(NSRange, toRange)
@synchronized (_context) {
NSDictionary *structDefine = _registeredStruct[typeString];
if (structDefine) {
size_t size = sizeOfStructTypes(structDefine[@"types"]);
void *ret = malloc(size);
getStructDataWithDict(ret, valObj, structDefine);
[invocation setArgument:ret atIndex:i];
free(ret);
break;
}
}
break;
}
case '*':
case '^': {
if ([valObj isKindOfClass:[JPBoxing class]]) {
void *value = [((JPBoxing *)valObj) unboxPointer];
if (argumentType[1] == '@') {
if (!_TMPMemoryPool) {
_TMPMemoryPool = [[NSMutableDictionary alloc] init];
}
if (!_markArray) {
_markArray = [[NSMutableArray alloc] init];
}
memset(value, 0, sizeof(id));
[_markArray addObject:valObj];
}
[invocation setArgument:&value atIndex:i];
break;
}
}
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];
Block_release((__bridge void *)cb);
} 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) {
strcpy(returnType, @encode(double));
}
if (strcmp(returnType, @encode(JPFloat)) == 0) {
strcpy(returnType, @encode(float));
}
// 方法返回值处理
id returnValue;
if (strncmp(returnType, "v", 1) != 0) {
if (strncmp(returnType, "@", 1) == 0) {
void *result;
[invocation getReturnValue:&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;
} else {
returnValue = (__bridge id)result;
}
return formatOCToJS(returnValue);
} else {
switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
#define JP_CALL_RET_CASE(_typeString, _type) \
case _typeString: { \
_type tempResultSet; \
[invocation getReturnValue:&tempResultSet];\
returnValue = @(tempResultSet); \
break; \
}
JP_CALL_RET_CASE('c', char)
JP_CALL_RET_CASE('C', unsigned char)
JP_CALL_RET_CASE('s', short)
JP_CALL_RET_CASE('S', unsigned short)
JP_CALL_RET_CASE('i', int)
JP_CALL_RET_CASE('I', unsigned int)
JP_CALL_RET_CASE('l', long)
JP_CALL_RET_CASE('L', unsigned long)
JP_CALL_RET_CASE('q', long long)
JP_CALL_RET_CASE('Q', unsigned long long)
JP_CALL_RET_CASE('f', float)
JP_CALL_RET_CASE('d', double)
JP_CALL_RET_CASE('B', BOOL)
case '{': {
NSString *typeString = extractStructName([NSString stringWithUTF8String:returnType]);
#define JP_CALL_RET_STRUCT(_type, _methodName) \
if ([typeString rangeOfString:@#_type].location != NSNotFound) { \
_type result; \
[invocation getReturnValue:&result]; \
return [JSValue _methodName:result inContext:_context]; \
}
JP_CALL_RET_STRUCT(CGRect, valueWithRect)
JP_CALL_RET_STRUCT(CGPoint, valueWithPoint)
JP_CALL_RET_STRUCT(CGSize, valueWithSize)
JP_CALL_RET_STRUCT(NSRange, valueWithRange)
@synchronized (_context) {
NSDictionary *structDefine = _registeredStruct[typeString];
if (structDefine) {
size_t size = sizeOfStructTypes(structDefine[@"types"]);
void *ret = malloc(size);
[invocation getReturnValue:ret];
NSDictionary *dict = getDictOfStruct(ret, structDefine);
free(ret);
return dict;
}
}
break;
}
case '*':
case '^': {
void *result;
[invocation getReturnValue:&result];
returnValue = formatOCToJS([JPBoxing boxPointer:result]);
if (strncmp(returnType, "^{CG", 4) == 0) {
if (!_pointersToRelease) {
_pointersToRelease = [[NSMutableArray alloc] init];
}
[_pointersToRelease addObject:[NSValue valueWithPointer:result]];
CFRetain(result);
}
break;
}
case '#': {
Class result;
[invocation getReturnValue:&result];
returnValue = formatOCToJS([JPBoxing boxClass:result]);
break;
}
}
return returnValue;
}
}
return nil;
}
复制代码
ok,到这里一些基本的流程就已经走完了。你可以大致感受到,其中有很大的一部分代码是在做类型转换的工作。这转换的过程中还有很多的细节,比如可变对象的处理等等这里没有具体展开来讲。有兴趣可以研究研究。
总结
- js和native的交互用的是
JavaScriptCore
。 - 替换添加方法都是直接走的方法交换。不存在的方法不会添加。
- 方法的调用用的都是
NSInvocation
。 - 参数、返回值的格式都是通过编码来处理。
最后
看完JSPatch
的源码,加上最近一直在玩JSBox。有些想要自己实现一个简单的JSBox的冲动,定个小目标,准备最近好好看看JS,然后尝试尝试。
水文一篇,希望能帮到大家一点点。