关于 JSPatch 的实现原理,JSPatch 作者本人 bang 已经有一系列文章阐述: - JSPatch 实现原理详解 <一> 核心 - JSPatch 实现原理详解 <二> 细节 - JSPatch 实现原理详解 <三> 扩展 - JSPatch 实现原理详解 <四> 新特性 - JSPatch 实现原理详解 <五> 优化
这些文章是对 JSPatch 内部实现原理和细节诸如“require实现”、“property实现”、“self/super 关键字”、“nil处理”、“内存问题”等具体设计思路和解决方案的阐述,并没有对 JSPatch 源码进行解读。在未接触源码、不清楚整个热修复流程的情况下去读这几篇文章难免一头雾水,最好的方法是边读源码边对照上述文章,代码中不理解的地方可以去文章中寻找答案。 本文将从一个小demo入手,跟踪代码执行流程,从Cocoa层、JavaScript层、Native层对热修复流程中涉及到的重要步骤和函数进行解析。
引入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; }
修复成功!
[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 的功能结构:
接下来读取 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) }
原脚本代码经过正则处理后交由 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方法。
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
方法。
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
事件。
经过上一步处理, 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
。
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 最重要的特性来实现一些强大的功能。这里不得不佩服作者深厚的编程功底和各种精彩的奇思妙想。