源码信息

JSPatch是iOS平台上的热修复方案,至于什么是热修复以及使用JSPatch是否会被苹果官方拒绝上架,这里不做介绍,这篇博客只关注JSPatch 源码本身。带大家过下JSPatch的源码。

源码解析

用例情景

JSPatch核心代码很精简但是这不意味着简单,还是需要仔细啃才能够理解它的思想。

我们先看下JSPatchDemo中的用法例子:

[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];[JPEngine evaluateScript:script];

在例子中首先启动JPEngine,紧接着会去加载本地的demo.js。然后在JPEngine中执行本地的demo.js脚本。

我们看下原先的RootViewController JPViewController:

#import "JPViewController.h"

@implementation JPViewController

- (void)viewDidLoad {
[super viewDidLoad];
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 50)];
[btn setTitle:@"Push JPTableViewController" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(handleBtn:) forControlEvents:UIControlEventTouchUpInside];
[btn setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:btn];
}

- (void)handleBtn:(id)sender {

}

@end

整个界面上有一个按钮,点击它会执行handleBtn方法,但是handleBtn目前是一个空方法,但是如果你将整个demo项目跑起来后会发现点击的时候会弹出一个TableView。但是如果注释掉之前将的JPEngine相关代码,点击就不会有任何响应,所以可以确定是JPEngine搞的鬼。我们看下demo.js:

defineClass('JPViewController', {
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)
}
})

defineClass('JPTableViewController : UITableViewController <UIAlertViewDelegate>', ['data'], {
dataSource: function() {
var data = self.data();
if (data) return data;
var data = [];
for (var i = 0; i < 20; i ++) {
data.push("cell from js " + i);
}
self.setData(data)
return data;
},
numberOfSectionsInTableView: function(tableView) {
return 1;
},
tableView_numberOfRowsInSection: function(tableView, section) {
return self.dataSource().length;
},
tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("cell")
if (!cell) {
cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(0, "cell")
}
cell.textLabel().setText(self.dataSource()[indexPath.row()])
return cell
},
tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
return 60
},
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var alertView = require('UIAlertView').alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("Alert",self.dataSource()[indexPath.row()], self, "OK", null);
alertView.show()
},
alertView_willDismissWithButtonIndex: function(alertView, idx) {
console.log('click btn ' + alertView.buttonTitleAtIndex(idx).toJS())
}
})

即使你不懂JavaScript 看到上面的代码估计也可以猜出个大概,它重写了handleBtn方法,在点击的时候会push一个JPTableViewController页面。并且使用JavaScript语言实现了JPTableViewController。也就是说它可以通过动态加载一个js文件,并且在这个js文件中改变现有代码的行为。既然本地的js文件也是需要转换为string后放到JSPatchEngine引擎中执行,如果将这个文件放到服务端下发下去,那么就可以通过后台动态控制我们应用的行为了,是不是很诱人的功能,当然我们不会将它用于实现大需求,一般如果用于修修线上的一些紧急bug还是比较方便的,特别是苹果平台审核周期有时候会比较长。这种情况如果遇到线上的一些崩溃没有热修复,只能和苹果官方沟通来缩短审核的时间,紧急发布修复版本。

JSPatch 引擎初始化

+ (void)startEngine{

//1.判断是否存在 JSContext 类. ---> iOS 7.0 以下不支持 JavaScriptCore
if (![JSContext class] || _context) {
return;
}

//2.创建一个 JS 运行环境.
JSContext *context = [[JSContext alloc] init];

//3.为了使 JSPatch.js 可以访问 JPEngine 中定义的 C 函数,需为 context 注册 block.
//3.1 创建类.
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};

//3.2 类实现某协议.
context[@"_OC_defineProtocol"] = ^(NSString *protocolDeclaration, JSValue *instProtocol, JSValue *clsProtocol) {
return defineProtocol(protocolDeclaration, instProtocol,clsProtocol);
};

//3.3 js调用oc的实例方法.
context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {
return callSelector(nil, selectorName, arguments, obj, isSuper);
};

//3.4 js调用oc的类方法.
context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) {
return callSelector(className, selectorName, arguments, nil, NO);
};

//3.5 js 对象转 oc 对象.
context[@"_OC_formatJSToOC"] = ^id(JSValue *obj) {
return formatJSToOC(obj);
};

//3.6 oc 对象 转 js 对象.
context[@"_OC_formatOCToJS"] = ^id(JSValue *obj) {
return formatOCToJS([obj toObject]);
};

//3.7 获取对象的关联属性
context[@"_OC_getCustomProps"] = ^id(JSValue *obj) {
id realObj = formatJSToOC(obj);
return objc_getAssociatedObject(realObj, kPropAssociatedObjectKey);
};

// 3.8 给对象动态添加关联属性
context[@"_OC_setCustomProps"] = ^(JSValue *obj, JSValue *val) {
id realObj = formatJSToOC(obj);
objc_setAssociatedObject(realObj, kPropAssociatedObjectKey, val, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
};

//3.9 给 js 对象设置 weak.
context[@"__weak"] = ^id(JSValue *jsval) {
id obj = formatJSToOC(jsval);
return [[JSContext currentContext][@"_formatOCToJS"] callWithArguments:@[formatOCToJS([JPBoxing boxWeakObj:obj])]];
};

//3.10 给 js 对象设置 strong.
context[@"__strong"] = ^id(JSValue *jsval) {
id obj = formatJSToOC(jsval);
return [[JSContext currentContext][@"_formatOCToJS"] callWithArguments:@[formatOCToJS(obj)]];
};

//3.11 获取 oc 对象的父类.
context[@"_OC_superClsName"] = ^(NSString *clsName) {
Class cls = NSClassFromString(clsName);
return NSStringFromClass([cls superclass]);
};

//3.12 是否自动转换类型.
context[@"autoConvertOCType"] = ^(BOOL autoConvert) {
_autoConvert = autoConvert;
};

//3.13 oc number 转换为 string.
context[@"convertOCNumberToString"] = ^(BOOL convertOCNumberToString) {
_convertOCNumberToString = convertOCNumberToString;
};

//3.14 在JS中调用include方法,可以在一个JS文件中加载其他JS文件.
context[@"include"] = ^(NSString *filePath) {
NSString *absolutePath = [_scriptRootDir stringByAppendingPathComponent:filePath];
if (!_runnedScript) {
_runnedScript = [[NSMutableSet alloc] init];
}
if (absolutePath && ![_runnedScript containsObject:absolutePath]) {
[JPEngine _evaluateScriptWithPath:absolutePath];
[_runnedScript addObject:absolutePath];
}
};
//3.15 获取资源文件路径.
context[@"resourcePath"] = ^(NSString *filePath) {
return [_scriptRootDir stringByAppendingPathComponent:filePath];
};

//3.16 让 js 方法延迟执行.
context[@"dispatch_after"] = ^(double time, JSValue *func) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[func callWithArguments:nil];
});
};

//3.17 让js方法在 main queue dispatch async 执行.
context[@"dispatch_async_main"] = ^(JSValue *func) {
dispatch_async(dispatch_get_main_queue(), ^{
[func callWithArguments:nil];
});
};

// 3.18 让js方法在 main queue dispatch sync 执行.
context[@"dispatch_sync_main"] = ^(JSValue *func) {
if ([NSThread currentThread].isMainThread) {
[func callWithArguments:nil];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[func callWithArguments:nil];
});
}
};

//3.19 让js方法在 global queue dispatch async 执行.
context[@"dispatch_async_global_queue"] = ^(JSValue *func) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[func callWithArguments:nil];
});
};

//3.20 释放js创建的oc对象.
context[@"releaseTmpObj"] = ^void(JSValue *jsVal) {
if ([[jsVal toObject] isKindOfClass:[NSDictionary class]]) {
void *pointer = [(JPBoxing *)([jsVal toObject][@"__obj"]) unboxPointer];
id obj = *((__unsafe_unretained id *)pointer);
@synchronized(_TMPMemoryPool) {
[_TMPMemoryPool removeObjectForKey:[NSNumber numberWithInteger:[(NSObject*)obj hash]]];
}
}
};

//3.21 js调用oc方法进行打印.
context[@"_OC_log"] = ^() {
NSArray *args = [JSContext currentArguments];
for (JSValue *jsVal in args) {
id obj = formatJSToOC(jsVal);
NSLog(@"JSPatch.log: %@", obj == _nilObj ? nil : (obj == _nullObj ? [NSNull null]: obj));
}
};

//3.22 将js捕捉到的异常交给oc方法处理.
context[@"_OC_catch"] = ^(JSValue *msg, JSValue *stack) {
_exceptionBlock([NSString stringWithFormat:@"js exception, \nmsg: %@, \nstack: \n %@", [msg toObject], [stack toObject]]);
};

//4. 注册 JSContext 执行出现异常时的回调.
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
_exceptionBlock([NSString stringWithFormat:@"js exception: %@", exception]);
};

//5. 创建OC中的null对象,转换成js的null对象,并设置到JSContext实例让js代码可以获取.
_nullObj = [[NSObject alloc] init];
context[@"_OC_null"] = formatOCToJS(_nullObj);

//6. 保存 context.
_context = context;

//7. oc 中的 nil 对象.
_nilObj = [[NSObject alloc] init];

//8. 同步锁.
_JSMethodSignatureLock = [[NSLock alloc] init];
_JSMethodForwardCallLock = [[NSRecursiveLock alloc] init];

//9. 在 JSPatch 中注册过的结构体定义(键:结构体名).
_registeredStruct = [[NSMutableDictionary alloc] init];
_currInvokeSuperClsName = [[NSMutableDictionary alloc] init];

//10. 注册内存警告通知.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];

//11. 读取JSPatch.js,方便传入的js代码中使用JSPatch.js提供的函数.
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"JSPatch" ofType:@"js"];
if (!path) _exceptionBlock(@"can't find JSPatch.js");
NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding];

//12. 加载 JSPatch.js 中的所有 js 代码到JSContext.
if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
[_context evaluateScript:jsCore withSourceURL:[NSURL URLWithString:@"JSPatch.js"]];
} else {
[_context evaluateScript:jsCore];
}
}

由于JSPatch是基于JavaScriptCore的所以在进行JSPatch 引擎初始化的时候会先检查下当前系统中是否支持JavaScriptCore。iOS 7.0 以下不支持 JavaScriptCore 所以在iOS 7.0 以下调用startEngine不会继续执行,直接返回。否则会创建一个JSContext并开始往JSContext里面注册对应的block,这些block会在JSPatch.js 中被调用。然后会注册JSContext 执行出现异常时的回调.最后会加载 JSPatch.js 中的所有 js 代码到JSContext 运行。

在JSPatch.js中会向gloable环境下添加defineClass,require,defineProtocol,block,defineJSClass 这些方法,这些方法可以供我们在热修复js文件中调用。

我们再回到demo.js:

defineClass('JPTableViewController : UITableViewController <UIAlertViewDelegate>', ['data'], {
dataSource: function() {
var data = self.data();
if (data) return data;
var data = [];
for (var i = 0; i < 20; i ++) {
data.push("cell from js " + i);
}
self.setData(data)
return data;
},
numberOfSectionsInTableView: function(tableView) {
return 1;
},
tableView_numberOfRowsInSection: function(tableView, section) {
return self.dataSource().length;
},
tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("cell")
if (!cell) {
cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(0, "cell")
}
cell.textLabel().setText(self.dataSource()[indexPath.row()])
return cell
},
tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
return 60
},
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var alertView = require('UIAlertView').alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("Alert",self.dataSource()[indexPath.row()], self, "OK", null);
alertView.show()
},
alertView_willDismissWithButtonIndex: function(alertView, idx) {
console.log('click btn ' + alertView.buttonTitleAtIndex(idx).toJS())
}
})

上面的defineClass有三个参数,第一个参数是类定义的字符串,第二个[‘data’]代表的是一个类的属性列表,这里只有一个属性参数data。第三个参数是这个类的实例方法列表。

global.defineClass = function(declaration/*定义字符串*/, properties/*属性*/, instMethods/*实例方法*/, clsMethods/*类方法*/) {
var newInstMethods = {}/*用于存放新的实例方法*/, newClsMethods = {}/*用于存放新的类方法*/
if (!(properties instanceof Array)) {
clsMethods = instMethods
instMethods = properties
properties = null
}

if (properties) {
//为属性添加Getter/Setter方法
properties.forEach(function(name){
//在instMethods中添加属性的get方法
if (!instMethods[name]) {
instMethods[name] = _propertiesGetFun(name);
}
//在instMethods中添加属性的set方法
var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1);
if (!instMethods[nameOfSet]) {
instMethods[nameOfSet] = _propertiesSetFun(name);
}
});
}
//取出实际类名
var realClsName = declaration.split(':')[0].trim()

//将实例方法和类方法 都转化为 key:方法名 [参数个数:方法实现]的形式 存放在newInstMethods,newClsMethods
_formatDefineMethods(instMethods, newInstMethods, realClsName)
_formatDefineMethods(clsMethods, newClsMethods, realClsName)

//调用OC的defineClass 进行定义
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]
}
}
//将自定义的js热修复代码添加到js环境
_setupJSMethod(className, instMethods, 1, realClsName)
_setupJSMethod(className, clsMethods, 0, realClsName)

return require(className)
}

JSPatch.js 的defineClass方法中会调用 JSEngine startEngine方法中向JSContext注入的_OC_defineClass block。传入的是这个类的定义,以及实例方法,类方法。其中实例方法和类方法的结构如下:

key:方法名 [参数个数:方法实现]

我们先来看下_OC_defineClass再回过来看JSPatch.js

context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
//================================根据JSj脚本实例化出相对应的类====================================================
//1.使用 NSScanner 分离 classDeclaration.
NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];

NSString *className; //类名
NSString *superClassName; //父类名
NSString *protocolNames; //实现的协议名
[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];
}
}

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);
}
}
//====================================================================================
for (int i = 0; i < 2; i ++) {
BOOL isInstance = i == 0;
JSValue *jsMethods = isInstance ? instanceMethods: classMethods;
//3.若是添加实例方法,直接使用Class对象;
//若是添加类方法,需要获取元类.

Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);
//把js对象转换成OC的字典,从而可以取到方法名、参数个数、具体实现.
NSDictionary *methodDict = [jsMethods toDictionary];
for (NSString *jsMethodName in methodDict.allKeys) {
//方法名为键,一个数组为值。数组第一个元素为对应实现函数的参数个数,第二个元素是方法的具体实现。
JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
//第一个值为 参数个数
int numberOfArg = [jsMethodArr[0] toInt32];
//将方法名转换为selectorName
NSString *selectorName = convertJPSelectorString(jsMethodName);

if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {
selectorName = [selectorName stringByAppendingString:@":"];
}

//第二个值为 方法实现
JSValue *jsMethod = jsMethodArr[1];
if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {
// 4.如果要替换的类已经定义了该方法,直接对该方法替换和实现消息转发.
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) {
//5.2 上述两种情况都不满足.js端请求添加一个新的方法.
if (![[jsMethodName substringToIndex:1] isEqualToString:@"_"]) {
//方法名的处理:_改为:
NSMutableString *typeDescStr = [@"@@:" mutableCopy];
for (int i = 0; i < numberOfArg; i ++) {
[typeDescStr appendString:@"@"];
}
//构造一个typeDescription为"@@:\@*"的IMP.将这个IMP添加到类中.
overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
}
}
}
// 6.为该类添加两个方法,使js脚本拥有设置property的方法.
class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");

//// 7.返回字典给js脚本
return @{@"cls": className, @"superCls": superClassName};
}

我们以demo.js为例子,defineClass这里传入的classDeclaration为 JPTableViewController : UITableViewController 这个字符串。在defineClass中会从中提取类信息,父类信息,以及协议信息。
第二个参数instanceMethods和第三个参数classMethods分别为包含属性Setter/Getter在内的实例方法和类方法。上面提到了这两个参数在js方法中已经将它组合成

key:方法名 [参数个数:方法实现]

这种形式,我们以

tableView_numberOfRowsInSection: function(tableView, section) {
return self.dataSource().length;
},

为例子: key为 tableView_numberOfRowsInSection value为[2,tableView:numberOfRowsInSection:的实现] 我们知道OC中要构建一个selector需要知道这个方法是实例方法还是类方法,方法名,IMP
类名,IMP 都可以直接拿到,我们还缺一个关键的信息,就是selectorName. 我们从js带过来带的key为tableView_numberOfRowsInSection,它其实包含了selectorName所需要的必要信息,只不过它是以”“分隔,所以将将”“替换为”:”
然后检查通过”:”分隔的字符串数和传入的参数个数这个进行进行比较,如果少了则需要在最后加个”:”就可以拿到我们需要的selectorName了。

我们接下来看下热修复最关键的方法overrideMethod:

方法替换

/**
* 使用jsvalue中将要替换的方法实现来替换oc类中的方法实现
*
* @param cls 被替换的类
* @param selectorName 被替换实现的SEL
* @param function 在js中定义的将要替换的新的实现
* @param isClassMethod 是否类方法(如果是-->寻找MetaClass)
* @param typeDescription 被替换的实现方法的编码
*/
static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription) {
//1. 要重写的方法的SEL.
SEL selector = NSSelectorFromString(selectorName);

//2. 获取重写方法的具体实现函数的格式编码.
if (!typeDescription) {
Method method = class_getInstanceMethod(cls, selector);
typeDescription = (char *)method_getTypeEncoding(method);
}

//3.获取 class 中被重写 SEL 对应的原始IMP.
IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;

//4.准备进入消息转发处理的系统函数实现IMP.
IMP msgForwardIMP = _objc_msgForward;
//.......

//--------------------------------------将要修复类原来的forwardInvocaiton替换成ORIGforwardInvocation--------------------------
//--------------------------------------将要修复类的SEL替换成ORIGsel添加到类---------------------------------------------------
//6.将cls中原来 forwardInvocaiton: 的实现替换成 JPForwardInvocation:函数实现.
//class_replaceMethod()返回的是替换之前的 IMP.
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
if (originalForwardImp) {
//7.为cls添加新的SEL(ORIGforwardInvocation:),指向原始 forwardInvocation: 的实现IMP.
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
}
}

[cls jp_fixMethodSignature];
if (class_respondsToSelector(cls, selector)) {
NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName];
SEL originalSelector = NSSelectorFromString(originalSelectorName);
if(!class_respondsToSelector(cls, originalSelector)) {
//8.为cls添加新的SEL(ORIG...:)指向被替换方法的原始实现IMP.
class_addMethod(cls, originalSelector, originalImp, typeDescription);
}
}

//9.构造替换实现后的新SEL:(JP...)
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];

//10.记录新SEL对应js传过来的待替换目标方法的实现.
_initJPOverideMethods(cls);
_JSOverideMethods[cls][JPSelectorName] = function;

//11.替换原SEL的实现IMP为msgForwardIMP
//让被替换的方法调用时,直接进入“消息转发”流程(_objc_msgForward 或 _objc_msgForward_stret)
//这一步放到最后是为了避免在 overrideMethod 过程中调用原sel导致的线程问题.

// 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);
}

如果大家看过Aspect源码这部分会比较好理解,两者在这部分思路是类似的,首先会将当前类的forwardInvocaiton替换为JSPatch实现的JPForwardInvocation:如果原来的类已经有实现了forwardInvocaiton那么就会将forwardInvocaiton替换成ORIGforwardInvocation
当前方法的selectorName 替换为_JPselectorName,如果原来有这个方法了,这里是在覆盖原有的方法,那么会将原来的方法selectorName改为ORIGselectorName。最后将selector方法的实现替换为_objc_msgForward 或 _objc_msgForward_stret 从而在代码中调用这个方法的时候,会触发消息下发。
走forwardInvocaiton,也就是会走到JSPatch实现的JPForwardInvocation:来重新控制方法调用的规则。

static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation) {

//1.表示oc对象是否已经被释放
BOOL deallocFlag = NO;
id slf = assignSlf;
BOOL isBlock = [[assignSlf class] isSubclassOfClass : NSClassFromString(@"NSBlock")];

//2.获取invocation中参数的数量
NSMethodSignature *methodSignature = [invocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];

//3.转化调用的SEL为JPSEL(这是JSPatch中缓存JSValue* function的key格式)
NSString *selectorName = isBlock ? @"" : NSStringFromSelector(invocation.selector);
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];

//4.判断JPSEL是否有对应的js函数的实现,如果没有就走原始方法的消息转发的流程.
JSValue *jsFunc = isBlock ? objc_getAssociatedObject(assignSlf, "_JSValue")[@"cb"] : getJSFunctionInObjectHierachy(slf, JPSelectorName);
if (!jsFunc) {
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}
//5.1 初始化数组,存储NSInvacation中获取的参数列表,传给对应的js函数
NSMutableArray *argList = [[NSMutableArray alloc] init];
if (!isBlock) {
if ([slf class] == slf) {
//5.2 类方法:设置__clsName标识表明这是一个类对象
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) {
//5.3 要被释放的对象:使用assign来保存self指针
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else {
//5.4 使用 weak 保存self 指针
[argList addObject:[JPBoxing boxWeakObj:slf]];
}
}

//5.5 NSInvocation 对象的前两个参数是self和_cmd,所以直接从第3个参数开始获取
for (NSUInteger i = isBlock ? 1 : 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
//返回值如果是const,获取encoding来判断类型.
switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
//从invocation中获取参数,添加到argList中.
#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 '@': {
//id类型参数使用__unsafe__unretained
__unsafe_unretained id arg;
[invocation getArgument:&arg atIndex:i];
//block参数使用copy,_nilObj表示nil
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;
}
}
}

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;
}
}
//6.将上面获得的参数列表数组转化为对应的js对象数组
NSArray *params = _formatOCToJSList(argList);
char returnType[255];
strcpy(returnType, [methodSignature methodReturnType]);

// 7.获取返回值类型
// Restore the return type
if (strcmp(returnType, @encode(JPDouble)) == 0) {
strcpy(returnType, @encode(double));
}
if (strcmp(returnType, @encode(JPFloat)) == 0) {
strcpy(returnType, @encode(float));
}

//7.1 返回值是否为const,如果是,获取后面的encoding来判断类型
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;
}
}

if (_pointersToRelease) {
for (NSValue *val in _pointersToRelease) {
void *pointer = NULL;
[val getValue:&pointer];
CFRelease(pointer);
}
_pointersToRelease = nil;
}
//8.待替换的方法是 delloc 需要特殊处理:
if (deallocFlag) {
slf = nil;
Class instClass = object_getClass(assignSlf);
Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc"));
//获取原delloc imp 指针,调用delloc,防止内存泄漏.
void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
originalDealloc(assignSlf, NSSelectorFromString(@"dealloc"));
}
}

JPForwardInvocation 会先从 NSInvocation中取出selector 名称,现在selector名称之前加上_JP,然后看下当前对象是否有对应的方法,我们在overideMethod方法中了解到,如果我们热修复js脚本中有对应的方法,那么就会在该对象中多出一个_JP开头的方法,指向热修复补丁中的实现。
所以这里会先看下当前方法中是否有对应的补丁方法,如果没有就调用JPExecuteORIGForwardInvocation走原来的消息分发机制。

static void JPExecuteORIGForwardInvocation(id slf, SEL selector, NSInvocation *invocation) {
SEL origForwardSelector = @selector(ORIGforwardInvocation:);

if ([slf respondsToSelector:origForwardSelector]) {
NSMethodSignature *methodSignature = [slf methodSignatureForSelector:origForwardSelector];
if (!methodSignature) {
_exceptionBlock([NSString stringWithFormat:@"unrecognized selector -ORIGforwardInvocation: for instance %@", slf]);
return;
}
NSInvocation *forwardInv= [NSInvocation invocationWithMethodSignature:methodSignature];
[forwardInv setTarget:slf];
[forwardInv setSelector:origForwardSelector];
[forwardInv setArgument:&invocation atIndex:2];
[forwardInv invoke];
} else {
Class superCls = [[slf class] superclass];
Method superForwardMethod = class_getInstanceMethod(superCls, @selector(forwardInvocation:));
void (*superForwardIMP)(id, SEL, NSInvocation *);
superForwardIMP = (void (*)(id, SEL, NSInvocation *))method_getImplementation(superForwardMethod);
superForwardIMP(slf, @selector(forwardInvocation:), invocation);
}
}

如果有的话,会把NSInvocation中的参数提取出来,传递给对应的补丁方法。然后通过JP_FWD_RET_CALL_JS调用js方法。通过JP_FWD_RET_CALL_JS中的

jsval = [cb callWithArguments:args]

获取到返回值后设置到NSInvocation中,完成方法的替换。

比如我们上面介绍的例子中有一个handleBtn. 原来的方法实现是空实现,后面通过JSPatch的demo.js 覆盖了这个方法,我们梳理下这是怎么实现的:

由于我们原有对象已经有了这个方法,只不过这个方法是空的,所以在overideMethod方法中会将原来的方法的selector名称改为_ORIGHandleBtn,指向原来的实现:

- (void)handleBtn:(id)sender {

}

而会在该类中添加一个_JPhandleBtn,它的实现指向补丁js中的实现。
并且由于该对象没有实现forwardInvocaiton 所以会为该对象新增加一个JPForwardInvocation,并且将原来的对handleBtn的调用转换为_objc_msgForward。这一切工作完成后我们来看下整个调用过程:
我们点击按钮后会触发handleBtn,这时候由于handleBtn的实现被替换为_objc_msgForward所以会走消息分发途径,进而走到forwardInvocaiton,由于forwardInvocaiton的实现在上面被替换为JPForwardInvocation
在JPForwardInvocation中会先尝试将消息转发到_JPhandleBtn看下是否有这个实现,这里由于有这个实现所以就直接将invocation参数提取出来,传递给对应的js方法执行。这就达到了整个方法替换的目的。

如何在JS环境下执行OC方法

我们上面了解到了怎么通过消息转发来达到热修复的目的,接下来我们还有一个问题就是如何在JS中执行OC方法,我们看下_evaluateScript,它的作用是将补丁js方法加载到JSContext中执行。我们还是以demo.js为例子

+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL {
//1. script 不存在或当前 iOS 版本低于 7.0 退出.
if (!script || ![JSContext class]) {
_exceptionBlock(@"script is nil");
return nil;
}
//在执行脚本之前会自动调用startEngine
[self startEngine];

//2. 正则式构建 (?<!\\\\)\\.\\s*(\\w+)\\s*\\(
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]];
//4.将正则处理后的js代码加载到 context 执行.(进入 JavaScriptCore)
@try {
//转换 JPTableViewController.alloc().init() 为 JPTableViewController.__c("alloc")().__c("init")()
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;
}

demo.js加载后会先通过正则匹配处理后再送到JSContext中,我们看下匹配替换后的formatedScript是什么样的:

;(function(){try{
defineClass('JPViewController', {
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")()
self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES)
}
})

defineClass('JPTableViewController : UITableViewController <UIAlertViewDelegate>', ['data'], {
dataSource: function() {
var data = self.__c("data")();
if (data) return data;
var data = [];
for (var i = 0; i < 20; i ++) {
data.__c("push")("cell from js " + i);
}
self.__c("setData")(data)
return data;
},
numberOfSectionsInTableView: function(tableView) {
return 1;
},
tableView_numberOfRowsInSection: function(tableView, section) {
return self.__c("dataSource")().length;
},
tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
var cell = tableView.__c("dequeueReusableCellWithIdentifier")("cell")
if (!cell) {
cell = require('UITableViewCell').__c("alloc")().__c("initWithStyle_reuseIdentifier")(0, "cell")
}
cell.__c("textLabel")().__c("setText")(self.__c("dataSource")()[indexPath.__c("row")()])
return cell
},
tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
return 60
},
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var alertView = require('UIAlertView').__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")("Alert",self.__c("dataSource")()[indexPath.__c("row")()], self, "OK", null);
alertView.__c("show")()
},
alertView_willDismissWithButtonIndex: function(alertView, idx) {
console.__c("log")('click btn ' + alertView.__c("buttonTitleAtIndex")(idx).__c("toJS")())
}
})
}catch(e){_OC_catch(e.message, e.stack)}})();

大家可以发现所有的xxx.XXXX 都被替换成了xxx.__c(“XXXX),为什么需要这样转换呢?我们到JSPatch.js寻找这个答案:

__c: function(methodName) {
var slf = this

if (slf instanceof Boolean) {
return function() {
return false
}
}
if (slf[methodName]) {
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
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)
}
}

return function(){
var args = Array.prototype.slice.call(arguments)
//_methodFunc() 把相关信息传给OC,OC用 Runtime 接口调用相应方法,返回结果值,这个调用就结束了。
return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
}
},


* instance: 对象
* clsName: 类名
* methodName: 方法名
* args: 参数列表
* isSuper: 是否调用super父类的方法
* isPerformSelector:是否用performSelector方式调用
*/
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
if (!isPerformSelector) {
//处理得到OC中的方法SEL,参数
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 += ":"
}
}
//当前是否是一个实例,如果是实例调用_OC_callI否则调用_OC_callC
var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
_OC_callC(clsName, selectorName, args)
//获取OC方法执行完毕的返回值,并转化成JS对象
return _formatOCToJS(ret)
}

__c 里面会调用_methodFunc,_methodFunc方法里面会根据当前方法是实例方法还是类方法决定调用_OC_callI还是_OC_callC。最后将oc中的返回值转换为JS对象返回。
_OC_callI还是_OC_callC是在我们初始化JSPatch引擎的时候注入到JS中的。在js中调用_OC_callI或者_OC_callC会触发调用OC层的callSelector方法,通过它将js中的补丁方法中oc相关的方法交给oc层。

/**
* 完成oc中的方法调用
*
* @param className 类名(nil --> 表示实例方法)
* @param selectorName 方法SEL值
* @param arguments 方法执行参数
* @param instance 对象(js对象中的变量,如: var UIAlertView = { __clsName : 'UIAlertView'})
* @param isSuper 是否调用的是父类方法
*
* @return 方法执行后的结果值,返回给js代码中.
*/

#pragma mark -
static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper)
{
NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString];

///[1] 实例变量处理
if (instance) {
//1.将js封装的instance对象进行拆装,得到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]]) {
//如果不是类方法,但是传入的instance 是空的则返回 nil
return @{@"__isNil": @(YES)};
}
}

///[2] 参数处理
//2.将js封装的参数列表转为oc类型.
id argumentsObj = formatJSToOC(arguments);

///[2] 解包处理
if (instance && [selectorName isEqualToString:@"toJS"]) {
// 3.如果要执行的方法是"toJS",即转化为js类型,对于NSString/NSDictory/NSArray/NSData需进行特殊处理
// 因为JSPatch中需使用JPBoxing包装OC中的上述对象,防止JavaScriptCore.framework转换类型.
if ([instance isKindOfClass:[NSString class]] || [instance isKindOfClass:[NSDictionary class]] || [instance isKindOfClass:[NSArray class]] || [instance isKindOfClass:[NSDate class]]) {
return _unboxOCObjectToJS(instance);
}
}
///[3] 获取到Class对象
//4.根据类名与selectorName获得对应的类对象与selector
Class cls = instance ? [instance class] : NSClassFromString(className);

///[3] 获取到SEL对象
SEL selector = NSSelectorFromString(selectorName);

NSString *superClassName = nil;
//5.判断是否调用的是父类的方法,如果是,走父类的方法实现
if (isSuper) {
//5.1 定义新的SEL:SUPERSEL

///[4] 构建出SUPER_seletcorName
NSString *superSelectorName = [NSString stringWithFormat:@"SUPER_%@", selectorName];
SEL superSelector = NSSelectorFromString(superSelectorName);

///[5] 构建出superCls
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);

// 5.2 将SUPERSEL指向superIMP的实现
///[6] 添加superSelector方法
class_addMethod(cls, superSelector, superIMP, method_getTypeEncoding(superMethod));

//5.3 查找父类中是否有添加JPSEL的实现
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
JSValue *overideFunction = _JSOverideMethods[superCls][JPSelectorName];
if (overideFunction) {
// 如果有,进行imp替换
overrideMethod(cls, superSelectorName, overideFunction, NO, NULL);
}

selector = superSelector;
superClassName = NSStringFromClass(superCls);
}


NSMutableArray *_markArray;

//6.通过类对象与selector构造对应的NSMethodSignature签名
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;
}
//7.根据签名构造NSInvocation对象
invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
//8.为invocation对象设置target
[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];
}
// 9.为invocation对象设置selector
[invocation setSelector:selector];

//10.根据签名得知每个参数的实际类型
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
NSInteger inputArguments = [(NSArray *)argumentsObj count];
if (inputArguments > numberOfArguments - 2) {
//10.1 多参数方法仅支持 id 类型参数和 id 类型返回,直接revoke并返回.
// calling variable argument method, only support parameter type `id` and return type `id`
id sender = instance != nil ? instance : cls;
//msg_send方法
id result = invokeVariableParameterMethod(argumentsObj, methodSignature, sender, selector);
return formatOCToJS(result);
}

///参数处理
//10.2 将JS传递过来的参数进行对应的转换(如 NSNumber -> int),转换后为 NSInvoca
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;
//11.执行 invoke 方法,并且传递指定的参数
[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;
//12. 获取 invocation 运行返回值.
if (strncmp(returnType, "v", 1) != 0) {
if (strncmp(returnType, "@", 1) == 0) {
void *result;
[invocation getReturnValue:&result];
// 13. 将返回值封装成JS对应的对象并返回.

//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;
}

callSelector代码很长但是它所处理的任务比较比较明确,它主要是通过js传过来的参数来构建一个Invocation 通过[invocation invoke]来执行对应的方法。代码细节部分已经给出了较为详细的注解大家可以看下整个流程。

我们这里回顾下整个过程;

在启动JSPatch 引擎之后会向Context注入一系列的OC方法给JS回调。
紧接着加载JSPatch.js 它里面主要定义了一些关键方法,最主要的是__c,它会从中提取到OC消息转发所必须的一系列参数,通过_OC_callI或者_OC_callC 传给 OC 层, OC 层中调用callSelector 方法,通过js传过来的参数来构建一个Invocation 通过[invocation invoke]来执行对应的方法。
而在OC层中加载补丁js脚本后会将脚本中的xxx.XXXX 正则替换为xxx.__c(“XXXX”),这样通过上面的解释后调用OC层方法,如果忽略掉这部分的整个细节,整个过程是js 补丁脚本中的 xxx.XXXX 最后会转为 OC中的[invocation invoke]
是不是有点巧妙?


总结

这篇博客主要从如何通过消息转发机制来使用js补丁方法/类来替换现有的方法,从而达到热修复的目的。以及如何将js中OC相关的代码交给OC层来处理。第一部分思路有点和Aspect类似,大家可以细细体会下,很巧妙的一个方案。下面是JSPatch的一个简单的框图。

Contents
  1. 1. 源码信息
  2. 2. 源码解析