iOS 热更新解读(二)—— JSPatch 源码解析
了解了 JavaScriptCore 的使用方式,本篇从 demo 入手对 JSPatch 关键源码进行解读。
关于 JSPatch 的实现原理,JSPatch 作者本人 bang 已经有一系列文章阐述:
- JSPatch 实现原理详解 <一> 核心
- JSPatch 实现原理详解 <二> 细节
- JSPatch 实现原理详解 <三> 扩展
- JSPatch 实现原理详解 <四> 新特性
- JSPatch 实现原理详解 <五> 优化
这些文章是对 JSPatch 内部实现原理和细节诸如“require实现”、“property实现”、“self/super 关键字”、“nil处理”、“内存问题”等具体设计思路和解决方案的阐述,并没有对 JSPatch 源码进行解读。在未接触源码、不清楚整个热修复流程的情况下去读这几篇文章难免一头雾水,最好的方法是边读源码边对照上述文章,代码中不理解的地方可以去文章中寻找答案。
本文将从一个小demo入手,跟踪代码执行流程,从Cocoa层、JavaScript层、Native层对热修复流程中涉及到的重要步骤和函数进行解析。
JSPatch 使用流程
引入JSPatch,JSPatch 核心部分只有三个文件,十分精巧:
建立一个小demo,在ViewController
屏幕中央放置一个button,button 点击事件为空:
// in ViewController.m -----------------
- (IBAction)handle:(id)sender {
}
热修复js文件(main.js)内容就是添加这个点击事件(弹出一个AlertView
):
// in main.js ------------------------
defineClass('ViewController', {
handle: function(sender) {
require('UIAlertView');
var alert = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("alert", null, null, "ok", null, null);
alert.show();
}
})
js 文件编写方法查看JSPatch 基础用法。
didFinishLaunchingWithOptions:
中开启 JSPatch 引擎、执行 js 脚本:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 开启 JPEngine.
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
// 执行js脚本代码.
[JPEngine evaluateScript:script];
return YES;
}
修复成功!
修复 step 1:startEngine
[JPEngine startEngine];
该方法向JSContext
环境注册了一系列供js调用oc方法的block,这些 block 内部大多是 调用 runtime
相关接口的 static 函数。最终读取JSPatch.js
中的代码到JSContext
环境,使得main.js
可以调用JSPatch.js
中定义的方法。
调用关系大致如下:
main.js ---> JSPatch.js ---> OC Block ---> runtime
源码解读:
+ (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];
// 10. 注册内存警告通知.
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
// 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 的功能结构:
修复 step 2:__c()元函数
接下来读取main.js
代码后执行:
[JPEngine evaluateScript:script];
该接口并非直接将main.js
代码提交到JSContext
环境执行,而是先调用_evaluateScript: withSourceURL:
方法对main.js
原始代码做些修改。
源码解读:
+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL
{
// 1. script 不存在或当前 iOS 版本低于 7.0 退出.
if (!script || ![JSContext class]) {
_exceptionBlock(@"script is nil");
return nil;
}
[self startEngine];
// 2. 正则式构建 (?<!\\\\)\\.\\s*(\\w+)\\s*\\(
if (!_regex) {
_regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil];
}
// 3. 使用正则式处理 传入的 js代码 >>> 将 alloc()这样的函数调用 替换成 __c("alloc")()
NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{%@}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 {
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;
}
断点调试看一下script
经正则处理之后的结果:
;(function(){try{defineClass('ViewController', {
pushAlertView: function(sender) {
require('UIAlertView');
var alert = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")("alert", null, null, "ok", null, null);
alert.__c("show")();
}
})}catch(e){_OC_catch(e.message, e.stack)}})();
除了添加一些关键字和异常处理外,最大的变化在于所有函数调用变成了__c("function")
的形式。据作者讲这是JSPatch
开发过程中最核心的问题,该问题的解决方案也是JSPatch
中最精妙之处。
我们进行热修复期望的效果是这样:
但JS 对于调用没定义的属性/变量,只会马上抛出异常,而不像 OC/Lua/ruby 那样有转发机制。因此对于用户传入的js代码中,类似UIView().alloc().init()
这样的代码,js其实根本没办法进行处理。
一种解决方案是实现所有js类继承机制,每一个类和方法都事先定义好:
这种方案是不太现实的,为了调用某个方法需要把该类的所有方法都引进来,占用内存极高(NSObject
类有将近1000个方法)。
作者最终想出了第二种方案:
在 OC 执行 JS 脚本前,通过正则把所有方法调用都改成调用 __c() 函数,再执行这个 JS 脚本,做到了类似 OC/Lua/Ruby 等的消息转发机制。
UIView.alloc().init()
->
UIView.__c('alloc')().__c('init')()
给 JS 对象基类 Object 的 prototype
加上 c 成员,这样所有对象都可以调用到 c,根据当前对象类型判断进行不同操作:
__c: function(methodName) {
...
...
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
}
}
_methodFunc()
把相关信息传给OC,OC用 Runtime 接口调用相应方法,返回结果值,这个调用就结束了。
源码解读:
/**
* instance: 对象
* clsName: 类名
* methodName: 方法名
* args: 参数列表
* isSuper: 是否调用super父类的方法
* isPerformSelector:是否用performSelector方式调用
*/
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
if (!isPerformSelector) {
// 不是 performSelector方式的方法调用流程
// 处理得到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方法后的返回值
// 如果是获取一个OC对象,那么ret = {"__obj":OC对象},因为OC把对象返回给js之前会先包装成NSDictionary
var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
_OC_callC(clsName, selectorName, args)
// 获取OC方法执行完毕的返回值,并转化成JS对象
return _formatOCToJS(ret)
}
修复 step 3:global.defineClass
原脚本代码经过正则处理后交由JSContext
环境去执行:
[_context evaluateScript:formatedScript withSourceURL:resourceURL];
回过头看main.js
的代码(处理后的):
defineClass(`ViewController`,{instaceMethods...},{classMethods...})
参数依次为类名、实例方法列表、类方法列表。阅读global.defineClass
源码会发现defineClass
首先会分别对两个方法列表调用_formatDefineMethods
,该方法参数有三个:方法列表(js对象)、空js对象、真实类名:
var _formatDefineMethods = function(methods, newMethods, realClsName) {
for (var methodName in methods) {
if (!(methods[methodName] instanceof Function)) return;
(function(){
var originMethod = methods[methodName]
newMethods[methodName] = [originMethod.length, 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)
}
}]
})()
}
}
该段代码遍历方法列表对象的方法名,向js空对象中添加属性:方法名为键,一个数组为值。数组第一个元素为对应实现函数的参数个数,第二个元素是方法的具体实现。也就是说,_formatDefineMethods
将 defineClass
传递过来的js对象进行了修改:
{
methodName:function(args...){...}
}
-->
{
methodName:[argCount,function(args...){...新实现}]
}
1. 为什么要传递参数个数?
因为runtime
修复类的时候无法直接解析js实现函数,也就无法知道参数个数,但方法替换的过程需要生成方法签名,所以只能从js端拿到js函数的参数个数,并传递给OC。
2. 为什么要修改方法实现?
- 参数转化为js对象。这涉及到对象生命周期的管理,具体查看<实现原理一>4.对象持有/转换。
- self 处理。使得js修复代码中我们可以像在 OC 中一样使用self,具体查看<实现原理一>6.self 关键字。
args.splice(0,1)
删除前两个参数:
OC中进行消息转发,前两个参数是self
和selector
,实际调用js的具体实现的时候,需要把这两个参数删除。
我们可以使用 safari 对 JSPatch.js 进行调试(JS 断点调试)看看处理之后的newInstMethods
:
回到defineClass
,调用_formatDefineMethods
之后,拿着要重写的类名和经过处理的js对象,调用_OC_defineClass
,也就是OC端定义的block方法。
修复 step 4:_OC_defineClass
JPEngine
中的defineClass
对类进行真正的重写操作,将类名、selector
、方法实现(IMP)、方法签名等runtime
重写方法所需的基本元素提取出来。
源码解读:
/**
* 定义一个类/覆盖或新增一个方法.
*
* @param classDeclaration 类的声明(需要替换或者新增的类名:继承的父类名 <实现的协议1,实现的协议2>)
* @param instanceMethods {实例方法}
* @param classMethods {类方法}
*
* @return 返回@{@"cls": className, @"superCls": superClassName}
*/
static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
// 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;
// 2.获取该Class对象.
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};
}
// 2.1 该Class对象为nil,为JS端添加一个新的类.
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) {
// 遍历字典的key,即方法名,根据方法名取出的值还是JSValue对象,它代表的是数组,第一个值是参数的个数,第二个值是函数的实现.
JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
int numberOfArg = [jsMethodArr[0] toInt32];
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) {
// 5.1 遍历protocolsNames,依次获取协议对象和协议方法中的type和name
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的方法.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");
#pragma clang diagnostic pop
// 7.返回字典给js脚本
return @{@"cls": className, @"superCls": superClassName};
}
由源码可见,方法名、实现等处理好之后最终执行overrideMethod
方法。
修复 step 5:overrideMethod
overrideMethod
是实现“替换”的最后一步。通过调用一系列runtime 方法增加/替换实现的api,使用jsvalue
中将要替换的方法实现来替换oc类中的方法实现。
该函数做的事情比较多,一张图概括如下:
4.向class添加名为ORIG+selector,对应原始selector的IMP。
这一步是为了让js通过这个方法调用原来的实现。
5.向class添加名为ORIGforwardInvocation
的方法,实现是原始的forwardInvocation
的IMP。
这一步是为了保存forwardInvocation
的旧有实现,在新的实现中做判断,如果转发的方法是欲改写的,就走新逻辑,反之走原来的流程。
源码解读:
/**
* 使用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;
// 5.针对“非 arm64”架构,消息转发应使用 _objc_msgForward_stret 系统函数.
// 因为_objc_msgForward函数在cpu架构不是 arm64 时,处理返回值是一些特殊 struct 时可能造成 crash.
#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
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
// 6.将cls中原来 forwardInvocaiton: 的实现替换成 JPForwardInvocation:函数实现.
// class_replaceMethod()返回的是替换之前的 IMP.
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@:@");
}
}
#pragma clang diagnostic pop
[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导致的线程问题.
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);
}
至此,selector
具体实现 IMP 的替换工作已经完成了。接下来便可以分析一下点击button后的handle
事件。
调用 step 1:JPForwardInvocation
经过上一步处理,handle:
直接走objc_msgForward
进行消息转发环节。当点击button,调用handle:
的时候,函数调用的参数会被封装到NSInvocation
对象,走到forwardInvocation
方法。上一步中forwardInvocation
方法的实现替换成了JPForwardInvocation
,负责拦截系统消息转发函数传入的NSInvocation
并从中获取到所有的方法执行参数值,是实现替换和新增方法的核心。
源码解读:
/**
* 替换原有的forwarInvocation:方法
*
* @param assignSlf self
* @param selector 原始SEL
* @param invocation 封装了函数调用参数的NSInvocation对象
*/
static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
{
// 1.表示oc对象是否已经被释放
BOOL deallocFlag = NO;
id slf = assignSlf;
// 2.获取invocation中参数的数量
NSMethodSignature *methodSignature = [invocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];
// 3.转化调用的SEL为JPSEL(这是JSPatch中缓存JSValue* function的key格式)
NSString *selectorName = NSStringFromSelector(invocation.selector);
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
// 4.判断JPSEL是否有对应的js函数的实现,如果没有就走原始方法的消息转发的流程.
JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);
if (!jsFunc) {
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}
// 5.从NSInvocation中获取调用的参数,把self与相应的参数都转换成js对象并封装到一个集合中
// js端重写的函数,传递过来是JSValue类型,用callWithArgument:调用js方法,参数也要是js对象.
// 5.1 初始化数组,存储NSInvacation中获取的参数列表,传给对应的js函数
NSMutableArray *argList = [[NSMutableArray alloc] init];
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(http://stackoverflow.com/questions/5788346/calling-a-selector-with-unknown-number-of-arguments-using-reflection-introspec)
// 所以直接从第3个参数开始获取
for (NSUInteger i = 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) {
Class cls = NSClassFromString(_currInvokeSuperClsName);
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, 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];
// 7.获取返回值类型
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));
}
// 7.1 返回值是否为const,如果是,获取后面的encoding来判断类型
switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
...(返回值的其它封装规则,具体看源码)
}
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"));
}
}
接下来执行JS中定义的方法实现。“修复 step 2”中已经讨论过,现在main.js中所有的函数都被替换成名为__c('methodName')
的函数调用,__c
调用了_methodFunc
函数,_methodFunc
会根据方法类型调用_OC_call
:
var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
_OC_callC(clsName, selectorName, args)
_OC_callI
或_OC_callC
最终都会调用一个static
函数callSelector
。
调用 step 2:callSelector
main.js
中类似UIAlertView.alloc().init()
实际是通过callSelector
调用 OC 的方法。
- 将 js 对象和参数转化为 OC 对象;
- 判断是否调用的是父类的方法,如果是,就走父类的方法实现;
- 把参数等信息封装成NSInvocation对象,并执行,然后返回结果。
源码解读:
/**
* 完成oc中的方法调用
*
* @param className 类名(nil --> 表示实例方法)
* @param selectorName 方法SEL值
* @param arguments 方法执行参数
* @param instance 对象(js对象中的变量,如: var UIAlertView = { __clsName : 'UIAlertView'})
* @param isSuper 是否调用的是父类方法
*
* @return 方法执行后的结果值,返回给js代码中.
*/
static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper)
{
NSString *realClsName = [[instance valueForProperty:@"__realClsName"] toString];
if (instance) {
// 1.将js封装的instance对象进行拆装,得到oc对象.
instance = formatJSToOC(instance);
if (!instance || instance == _nilObj || [instance isKindOfClass:[JPBoxing class]]) return @{@"__isNil": @(YES)};
}
// 2.将js封装的参数列表转为oc类型.
id argumentsObj = formatJSToOC(arguments);
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);
}
}
// 4.根据类名与selectorName获得对应的类对象与selector
Class cls = instance ? [instance class] : NSClassFromString(className);
SEL selector = NSSelectorFromString(selectorName);
NSString *superClassName = nil;
// 5.判断是否调用的是父类的方法,如果是,走父类的方法实现
if (isSuper) {
// 5.1 定义新的SEL:SUPERSEL
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);
// 5.2 将SUPERSEL指向superIMP的实现
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并返回.
id sender = instance != nil ? instance : cls;
id result = invokeVariableParameterMethod(argumentsObj, methodSignature, sender, selector);
return formatOCToJS(result);
}
// 10.2 将JS传递过来的参数进行对应的转换(如 NSNumber -> int),转换后为 NSInvocation 对象设置参数.
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]) {
//...(具体转换规则看源代码)
}
}
if (superClassName) _currInvokeSuperClsName = superClassName;
// 11.执行 invoke 方法,并且传递指定的参数
[invocation invoke];
if (superClassName) _currInvokeSuperClsName = nil;
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;
// 12. 获取 invocation 运行返回值.
[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]) {
//...(具体转换规则见源代码)
return returnValue;
}
}
return nil;
}
至此,JSPatch 热修复核心步骤方法替换和方法调用就结束了。
总结
JSPatch 基于JavaScriptCore.framework
和Objective-C中的runtime技术。
- 采用 iOS7 后引入的
JavaScriptCore.framework
作为 JavaScript 引擎解析js脚本,执行js代码并与OC端代码进行桥接。 - 使用Objective-C
runtime
中的method swizzling
方式达到使用js脚本动态替换原有OC方法的目的,并利用forwardInvocation
消息转发机制使得在js脚本中调用OC的方法成为可能。
JSPatch 实现过程中还有许多细节问题诸如 Special Struct、内存管理、JPBoxing
、nil
处理等,更多详细内容可以阅读作者的原理详解系列文章以及GitHub wiki。学习 JSPatch 不仅可以弄清 iOS 热修复机制,也可以体会到如何利用 runtime 这一 OC 最重要的特性来实现一些强大的功能。这里不得不佩服作者深厚的编程功底和各种精彩的奇思妙想。