JSPatch一款优秀的热更新框架,最近由于各种原因被苹果封杀了,虽然我们开发者暂时不能使用这种方式来进行来热更新,但是代码还是值得我们好好学习一下的,尤其是里面对于Runtime的使用,我们可以把它当做是Runtime的实践教材。
当然热更新的方式还有很多,Weex,React Native, Hybrid ... 好像苹果现在并没有禁止JavaScriptCore引擎下的代码下发。
其实框架的实现原理,作者已经写的很详细了,本文主要是从源码的角度来解析实现过程。
下面根据调用流程来看一下,具体的是怎么实现的。
本文JS代码的调试是使用的Safari,具体步骤:
Safari->开发->Simulator->选择当前执行的js
使用方式
举个栗子,比如有一个bug:
- (IBAction)btnClick:(id)sender {
NSArray *arrTest = @[@"1"];
@try {
NSString *strCrash = [arrTest objectAtIndex:2];
NSLog(@"strCrash %@", strCrash);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Click" message:@"Success" delegate:nil cancelButtonTitle:@"Yes" otherButtonTitles:nil, nil];
[alert show];
} @catch (NSException *exception) {
NSLog(@"exception is %@", exception);
} @finally {
NSLog(@"finally");
}
}
经典的数组越界问题,当然这里添加了try catch并不会崩溃,但是会抛出异常。
使用JSPatch下发main.js代码如下:
require("UIAlertView");
defineClass("ViewController", {
btnClick: function(sender) {
var arrTest = ["arrTest"];
var strCrash = arrTest[0];
var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("JSPatchAmend", "Success", null, "Yes", null, null);
alert.show();
}
}, {}, {});
当然我们现在没有JS代码下发的逻辑,所以模拟一下JS代码的执行,main.js先加到工程中:
- (void)testJSPatch {
NSString *strJsPath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
NSLog(@"strJsPath %@", strJsPath);
JSValue *resultValue = [JPEngine evaluateScriptWithPath:strJsPath];
NSLog(@"resultValue %@", resultValue);
}
追踪代码
首先我们调用的是JPEngine类的evaluateScriptWithPath方法
+ (JSValue *)evaluateScriptWithPath:(NSString *)filePath
{
_scriptRootDir = [filePath stringByDeletingLastPathComponent];
return [self _evaluateScriptWithPath:filePath];
}
+ (JSValue *)_evaluateScriptWithPath:(NSString *)filePath
{
NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 获取JS代码 字符串
return [self _evaluateScript:script withSourceURL:[NSURL URLWithString:[filePath lastPathComponent]]];
}
+ (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];
}
// js字符串代码格式转换:新增了try catch,防止崩溃,使用正则 替换方法__c(),方法的调用统一走__c()方法
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]; // 调用JS
} else {
return [_context evaluateScript:formatedScript];
}
}
@catch (NSException *exception) {
_exceptionBlock([NSString stringWithFormat:@"%@", exception]);
}
return nil;
}
这里获取到JS代码字符串之后,开始调用+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL 方法执行调用。
方法逻辑:
调用startEngine 方法(初始化参数,并调用JSPatch.js)
使用正则替换替换所有方法调用为 __c() 的形式,并添加try-catch语句防止崩溃
执行main.js代码,并使用_exceptionBlock抛出异常
看一下startEngine方法
+ (void)startEngine
{
if (![JSContext class] || _context) { // 如果已经初始化_context就返回,不需要重新初始化
return;
}
JSContext *context = [[JSContext alloc] init];
// 提前注册JS方法调用,js调用对应方法的时候回调给OC方法
#ifdef DEBUG
context[@"po"] = ^JSValue*(JSValue *obj) {
id ocObject = formatJSToOC(obj);
return [JSValue valueWithObject:[ocObject description] inContext:_context];
};
context[@"bt"] = ^JSValue*() {
return [JSValue valueWithObject:_JSLastCallStack inContext:_context];
};
#endif
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) { // call instace method
return callSelector(nil, selectorName, arguments, obj, isSuper);
};
context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { // call class method
return callSelector(className, selectorName, arguments, nil, NO);
};
...
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
_exceptionBlock([NSString stringWithFormat:@"js exception: %@", exception]);
};
_nullObj = [[NSObject alloc] init];
context[@"_OC_null"] = formatOCToJS(_nullObj);
_context = context;
// 初始化
_nilObj = [[NSObject alloc] init];
_JSMethodSignatureLock = [[NSLock alloc] init];
_JSMethodForwardCallLock = [[NSRecursiveLock alloc] init];
_registeredStruct = [[NSMutableDictionary alloc] init];
_currInvokeSuperClsName = [[NSMutableDictionary alloc] init];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
// 运行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];
if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
[_context evaluateScript:jsCore withSourceURL:[NSURL URLWithString:@"JSPatch.js"]];
} else {
[_context evaluateScript:jsCore];
}
}
方法逻辑:
- 提前注册JS调用OC的方法,具体如下
_OC_defineClass, 定义类
_OC_defineProtocol,定义协议
_OC_callI,调用实例方法
_OC_callC,调用类方法
_OC_formatJSToOC,格式化JS为OC类型
_OC_formatOCToJS,格式化OC为JS类型
_OC_getCustomProps,获取属性get方法
_OC_setCustomProps,获取属性set方法
__weak,__weak属性
__strong, __strong 属性
_OC_superClsName,获取父类名
resourcePath, 获取js文件路径
dispatch_after,GCD延时调用
dispatch_async_main,GCD异步主队列
dispatch_sync_main,GCD同步主队列
dispatch_async_global_queue,GCD全局队列
releaseTmpObj,释放tmp对象
_OC_log, log信息
_OC_null, 空对象
初始化一些变量 _nilObj,_JSMethodSignatureLock,_JSMethodForwardCallLock...
加载JSPatch.js代码
执行main.js代码的时候使用的是JSpatch.js里面的defineClass()方法
看一下JSPatch.js中的defineClass()方法
// declaration: 类名,父类,协议的描述,cls:supercls
// properties: { 方法名:JS的方法实现 }
// instMethods
global.defineClass = function(declaration, properties, instMethods, clsMethods) {
var newInstMethods = {}, newClsMethods = {}
if (!(properties instanceof Array)) { // properties 不是数组,是字典类型: {方法名:方法体},直接赋值给 instMethods,然后置空
clsMethods = instMethods
instMethods = properties
properties = null
}
/*
逻辑:如果是属性那么使用数组[property1,property2], 再动态获取set,get方法然后放到instMethods字典中,也就是instMethods中存的是{方法名:方法实现}
*/
if (properties) { // 此时 properties 应该是数组,那么处理OC属性Property相关,
properties.forEach(function(name){
if (!instMethods[name]) {
instMethods[name] = _propertiesGetFun(name); // 设置property的get方法
}
var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1); // set方法
if (!instMethods[nameOfSet]) {
instMethods[nameOfSet] = _propertiesSetFun(name); // 设置property的set方法
}
});
}
// 获取真实的类名
var realClsName = declaration.split(':')[0].trim() // split 把一个字符串分割成字符串数组
_formatDefineMethods(instMethods, newInstMethods, realClsName)
_formatDefineMethods(clsMethods, newClsMethods, realClsName)
var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods) // oc构造类,返回类名,父类名 返回值:@{@"cls": className, @"superCls": superClassName};
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]
}
}
// className: OC定义的类名,instMethods:实例方法{方法名:方法实现},instMethods:解析declaration获取的真实类名
// 对js代码进行了一次包装,_wrapLocalMethod
_setupJSMethod(className, instMethods, 1, realClsName)
_setupJSMethod(className, clsMethods, 0, realClsName)
return require(className) // 返回的是: {__clsName: 类名}
}
方法逻辑:
-
处理传入的JS方法
properties传入的不是数组类型,那么就是方法类型:{方法名:方法实现},直接赋值给instMethods,并置nil。
-
处理属性方法数组
properties传入的是数组类型,那么处理这些属性,动态生成set,get方法,使用的是Runtime的关联对象方法。
-
格式化实例方法,类方法
格式化类方法,实例方法,将方法封装成数组 [1, function()], 1 为 originMethod.length(var originMethod = methods[methodName] )根据OC代码来看应该是参数个数, function()方法对之前的方法进行了封装,封装方法跟下面的_setupJSMethod一样。
-
调用OC的_OC_defineClass方法执行定义类
下面解析这个OC方法,返回类型是:
{"cls": className, "superCls": superClassName}; 如果本地缓存_ocCls中有父类,将继承所有父类方法。
设置JS方法,进行了一次包装
最后返回类名的字典:{__clsName: 类名}
穿插中间调用JPEngine.m中的_OC_defineClass()方法
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods); // 类名, 实例方法,类方法
};
static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
// classDeclaration: 原始的类名 + :父类 + '<>'协议
// 通过runtime定义类,并返回类名,父类名
NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];
NSString *className;
NSString *superClassName;
NSString *protocolNames; // class:superclass 扫描获取className,superClassName,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"; // 默认父类都是NSObject
className = trim(className); // 去空格
superClassName = trim(superClassName);
NSArray *protocols = [protocolNames length] ? [protocolNames componentsSeparatedByString:@","] : nil;
Class cls = NSClassFromString(className);
if (!cls) { // 转换不了此类,动态新增一个NSObject的子类
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; // 方法
// instanceMethods 字典 key:方法名, value:数组(0:参数个数 1:js代码???)
Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String); // 类名
NSDictionary *methodDict = [jsMethods toDictionary];
for (NSString *jsMethodName in methodDict.allKeys) {
JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
/*
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) // 删除第一个元素
var ret = originMethod.apply(originMethod, args)
global.self = lastSelf
return ret
} catch(e) {
_OC_catch(e.message, e.stack)
}
}
*/
int numberOfArg = [jsMethodArr[0] toInt32];
NSString *selectorName = convertJPSelectorString(jsMethodName);
if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) { // 这个地方一个参数可以添加一个':',如果是多个参数呢?怎么添加?
selectorName = [selectorName stringByAppendingString:@":"];
}
JSValue *jsMethod = jsMethodArr[1]; // 来自js代码
if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) { // 类中含有这个方法,替换方法,跳转拦截调用
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]);
}
}
}
}
}
// 新增两个方法: setProp: getProp: forKey:
class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
return @{@"cls": className, @"superCls": superClassName};
}
方法逻辑:
- 解析classDeclaration中的类名,父类,协议
- 没有父类,默认是NSObject
- 如果这个类不是系统类,调用runtime的objc_registerClassPair新建
- 如果有protocols,动态添加协议列表
- 添加实例方法,类方法
双重for循环,第一次是对类操作,添加实例方法,第二次是对元类操作,添加类方法。
遍历方法列表,获取jsMethodArr第0个位置:参数个数,为方法动态添加":"
如果本类中,已经有这个方法,那么执行overrideMethod()方法替换,跳转拦截调用,下面解析
如果本类中,没有这个方法,可能是协议,先获取协议方法的参数类型typeDescription,再执行overrideMethod()方法
如果不是本类中的方法,也不是协议,那么需要获取到这个方法的参数类型typeDescription,再执行overrideMethod()
为类新增set,get方法,统一使用关联对象关联property
返回类名,父类名
overrideMethod()到底做了些什么?
// 方法替换方法 _objc_msgForward,所有的方法全部都跳转到拦截调用
/*
* 新增了一个ORIGFunc方法,方法实现是原来的方法实现,添加到本类中
* 新增一个JSFunc名字, 将方法名添加_JP前缀作为key,将JS代码作为value存入_JSOverideMethods中, 为以后JS调用做存储
*/
static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
{
SEL selector = NSSelectorFromString(selectorName);
if (!typeDescription) { // 如果没有传typeDescription,就是类中有这个方法,那么可以直接通过方法获取typeDescription
Method method = class_getInstanceMethod(cls, selector);
typeDescription = (char *)method_getTypeEncoding(method);
}
IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;
IMP msgForwardIMP = _objc_msgForward; // 用_objc_msgForward函数指针代替imp,_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
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
// 如果不是本地的forwardInvocation实现,新增JPForwardInvocation方法到类中,原来的forwardInvocation方法实现更改为ORIGforwardInvocation下。
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
if (originalForwardImp) {
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
}
}
[cls jp_fixMethodSignature]; // fix bug
if (class_respondsToSelector(cls, selector)) { // 类中含有这个方法,将原来的方法新增一个ORIG前缀,新增到类中, ORIG方法的实现是原方法
NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName]; // ORIGFunc
SEL originalSelector = NSSelectorFromString(originalSelectorName);
if(!class_respondsToSelector(cls, originalSelector)) {
class_addMethod(cls, originalSelector, originalImp, typeDescription);
}
}
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName]; // _JPFunc
_initJPOverideMethods(cls); // 初始化_JSOverideMethods
_JSOverideMethods[cls][JPSelectorName] = function;
// 将方法名添加_JP前缀作为key,将js代码作为value存入_JSOverideMethods中
// 存储JS代码: _JSOverideMethods字段: {类 = {"_JPFunc" = "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); // 将方法替换为_objc_msgForward,调用直接跳转拦截调用
方法逻辑:
- 如果没有传typeDescription,就是类中有这个方法,那么可以直接通过方法获取typeDescription
- 获取方法是原来实现originalImp,以及设置拦截调用IMP:_objc_msgForward
- 如果不是本地的forwardInvocation实现,新增JPForwardInvocation方法到类中,原来的forwardInvocation方法实现更改为ORIGforwardInvocation下
- 如果类中含有这个方法,将原来的方法新增一个ORIG前缀,新增到类中, ORIG方法的实现是原方法
- 保存一份方法:将方法名新增_JP前缀,添加到全局对象_JSOverideMethods字典中:{类 = {"_JPFunc" = "function实现"}}
- 执行方法替换,将当前方法selector的实现替换成msgForwardIMP,也就是肯定会跳转拦截调用,在拦截调用中捕获参数信息。
执行到这里,我们看到JS里面保存了一份方法的实现,OC里面也存了一份。
JS中:_ocCls
OC中:_JSOverideMethods
再次触发崩溃按钮,跟踪一下代码
- (IBAction)btnClick:(id)sender
根据上面的解析,应该会走拦截调用方法,而我们已经将拦截调用的方法实现IMP替换成JPForwardInvocation()方法。接下来看一下这个方法。
拦截调用方法JPForwardInvocation()
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]; // _JPFunc
JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName); // 获取之前存在 _JSOverideMethods字典中对应JS方法的实现
if (!jsFunc) { // 如果没有这个方法实现,那么跳转系统自己的拦截调用方法,或者是工程中有实现的拦截调用方法,但是问题是如果有方法实现,那么工程中的拦截调用如果重写了,那么就不会执行ForwardInvocation方法了!!!!
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}
NSMutableArray *argList = [[NSMutableArray alloc] init]; // 存类型数组
if ([slf class] == slf) { // slf 是 类 类型
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) { // selectorName是dealloc方法
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else { // 正常的对象类型
[argList addObject:[JPBoxing boxWeakObj:slf]];
}
// 对参数进行解析,从i=2开始,就是对参数解析。 methodSignature: 返回值, 各个参数
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
...
case '@': { // 对象类型
__unsafe_unretained id arg;
[invocation getArgument:&arg atIndex:i]; // 获取当前对象,例子中是UIButton类型 >
if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {
[argList addObject:(arg ? [arg copy]: _nilObj)]; // NSBlock 对象需要执行copy,猜测是防止释放吧
} else {
[argList addObject:(arg ? arg: _nilObj)]; // 添加到argList
}
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;
}
}
NSArray *params = _formatOCToJSList(argList); // 转换成JS类型的对象,格式:[{"__clsName": 类名, "__obj": 对象}, ...]
char returnType[255];
strcpy(returnType, [methodSignature methodReturnType]); // 获取返回值类型 此处v:void
// Restore the return type
if (strcmp(returnType, @encode(JPDouble)) == 0) { // 根据JS类型转换double
strcpy(returnType, @encode(double));
}
if (strcmp(returnType, @encode(JPFloat)) == 0) { // 根据JS类型转换float
strcpy(returnType, @encode(float));
}
// 处理返回值类型
switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
... // 此处省略占篇幅的宏定义
case 'v': {
// JP_FWD_RET_CALL_JS // 由于用宏不好调试,替换成下面代码
JSValue *jsval;
[_JSMethodForwardCallLock lock]; // 加锁
jsval = [jsFunc callWithArguments:params]; // 执行js方法
[_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];
}
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;
}
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"));
}
}
上面代码中有些...是省略了部分代码
方法逻辑:
获取当前替换方法调用方法的参数,拦截调用的主要作用在于此。否则直接使用Method Swizzle即可,何必再走拦截调用呢。
通过在方法中添加_JP前缀,获取之前存在_JSOverideMethods中对应的JS方法实现,如果为空直接调用原来的拦截调用方法。
将类,对象,各个参数转换为OC对象存入argList数组中
处理父类方法
处理返回值
根据返回值类型,调用JS代码[jsFunc callWithArguments:params],也就是此处会调用之前封装过的JS代码,执行JS中的btnClick:方法,这个地方比较绕。封装过的JS代码不是很理解。JS功底比较好的同学可以指教一下。
执行我们main.js里面btnClick:方法
btnClick: function(sender) {
var arrTest = ["arrTest"];
var strCrash = arrTest[0];
var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("JSPatchAmend", "Success", null, "Yes", null, null);
alert.show();
}
其实已经替换成了
{
var arrTest = ["arrTest"];
var strCrash = arrTest[0];
var alert = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")("JSPatchAmend", "Success", null, "Yes", null, null);
alert.__c("show")();
}
我们把crash代码替换掉,并执行了一个原生的Alert,这里统一的方法调用都是调用__c()
__C()方法的调用
疑问:JS如何去调用__c()方法呢?
for (var method in _customMethods) { // __c
if (_customMethods.hasOwnProperty(method)) {
// Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
// Object.prototype 属性表示 Object 的原型对象。
Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false})
}
}
上面for循环我们可以看到,这里将_customMethods字典里面的key都会设置成这个对象的属性,由于这段代码没有调试出来,所以暂时理解为所有对象都添加__c、super、performSelectorInOC、performSelector方法,而__c方法实现是一个匿名函数, 返回值又是一个匿名函数
下面为_customMethods字典的内容:
var _customMethods = { // 字典对象,存 __c: function
__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); // 通过调用oc的[cls superclass] 方法获取父类的名字
}
var clsName = slf.__clsName
if (clsName && _ocCls[clsName]) { // 有缓存,返回缓存的方法
var methodType = slf.__obj ? 'instMethods': 'clsMethods' // 通过__obj判断是实例方法还是类方法
if (_ocCls[clsName][methodType][methodName]) {
slf.__isSuper = 0;
return _ocCls[clsName][methodType][methodName].bind(slf) // // 返回的是之前缓存的方法
}
}
return function(){
var args = Array.prototype.slice.call(arguments) // 转换成数组, arguments 怎么获取的??
return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper) // 返回方法调用的结果
}
},
super: function() {
var slf = this
if (slf.__obj) {
slf.__obj.__realClsName = slf.__realClsName;
}
return {__obj: slf.__obj, __clsName: slf.__clsName, __isSuper: 1}
},
performSelectorInOC: function() {
var slf = this
var args = Array.prototype.slice.call(arguments)
return {__isPerformInOC:1, obj:slf.__obj, clsName:slf.__clsName, sel: args[0], args: args[1], cb: args[2]}
},
performSelector: function() {
var slf = this
var args = Array.prototype.slice.call(arguments)
return _methodFunc(slf.__obj, slf.__clsName, args[0], args.splice(1), slf.__isSuper, true)
}
}
注意:
方法调用例如:UIAlertView.__c("fucName")("param")
第一个调用为方法名:("fucName"),参数为fucName,执行的是以__c 为key的匿名函数,此时获取的返回值是另一个匿名函数,再执行("param")则是调用的返回值的匿名函数,参数为param。
解析一下__c()匿名函数的逻辑:
做了异常处理
处理父类方法
如果这个JS方法在缓存_ocCls字典中,那么直接返回
如果没有缓存,那么调用_methodFunc方法
下面看下_methodFunc方法
_methodFunc()方法
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
if (!isPerformSelector) { // 如果不是performselector,用正则对selectorName进行字符替换:__ 替换 -,- 替换 _,有':', 在最后添加上':'
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的生成方法:实例方法还是类方法
return _formatOCToJS(ret)
}
作用:通过调用OC方法,实现方法调用,并返回调用结果
方法逻辑:
- 首先执行方法替换:
__ 替换 -,- 替换 _,有':', 在最后添加上':'
根据是否是实例对象来调用OC的方法_OC_callI(实例方法),_OC_callC(类方法)
获取返回值,并格式转换为JS类型
下面看下OC的方法调用_OC_callI,_OC_callC
OC调用方法执行_OC_callI/_OC_callC
context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { // call instace method
return callSelector(nil, selectorName, arguments, obj, isSuper); // 返回值类型: {"__clsName": 类名; "__obj": 对象}
};
context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { // call class method
return callSelector(className, selectorName, arguments, nil, NO);
};
可以看到,这两个方法都是调用的callSelector()方法。
下面看一下callSelector方法的实现:
static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper)
{
NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString];
if (instance) {
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); // 转换成oc对象数组
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)cls] = [[NSMutableDictionary alloc]init]; // 初始化key
}
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); // fix bug
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); // 调用objc_msgSend方法
return formatOCToJS(result);
}
for (NSUInteger i = 2; i < numberOfArguments; i++) { // 参数的处理,注意从第二个argument开始:0是方法名,1是':'
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
id valObj = argumentsObj[i-2];
switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
...
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];
} 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) { // 如果是JPDouble结构体,返回double的编码d
strcpy(returnType, @encode(double));
}
if (strcmp(returnType, @encode(JPFloat)) == 0) {
strcpy(returnType, @encode(float));
}
id returnValue;
if (strncmp(returnType, "v", 1) != 0) { // 不是void类型
if (strncmp(returnType, "@", 1) == 0) { // id 类型
void *result;
[invocation getReturnValue:&result]; // 获取返回值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; // 这几个参数alloc/new/copy/mutableCopy会生成新对象,并且引用计数是1,需要我们持有,否则会提前释放?释放时机在哪?
} else { // 一般类型的返回值:id
returnValue = (__bridge id)result;
}
return formatOCToJS(returnValue);
} else {
switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
...
case '#': {
Class result;
[invocation getReturnValue:&result];
returnValue = formatOCToJS([JPBoxing boxClass:result]);
break;
}
}
return returnValue;
}
}
return nil;
}
由于比较长,我们删掉了部分代码。
方法逻辑:
这里根据调用对象的类型来进行判断是否调用实例方法,还是类方法。并对参数进行转换成OC数组
处理父类方法
使用NSInvocation来执行方法调用,注意,类方法使用[cls methodSignatureForSelector:selector],而实例方法使用 [cls instanceMethodSignatureForSelector:selector]来初始化methodSignature。这里使用_JSMethodSignatureCache缓存methodSignature内容,提升方法调用效率
处理返回值,这里逻辑挺多的,核心就是调用Runtime的objc_msgSend方法,对于参数的转换,这里不做讨论。
至此,大体逻辑我们已经理清楚了。里面还有很多细节都写的挺好,还是值得去研究一下。
如果文中有什么错误,欢迎大家指正。
转载请注明出处:http://semyonxu.com
代码注释Demo见:
https://github.com/342261733/JSPatchAnalysis
typeDescription类型的含义可以参考苹果文档:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
参考JSPatch作者文档:
https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3