一统天下 —— pandoc
1.前言
前一段时间在公司做了一个iOS热补丁的模块,就用到了JSPatch框架,期间有了解过一些关于框架的源码分析的博客:
1.JSPatch学习:JSPatch核心和实现原理分析
2.JSPatch defineProtocol部分实现详解
思考再三还是决定自己调试了解一下整体的实现机制。
2.准备工具
3.项目文件
3.1项目文件图
3.2具体文件分析
- JPEngine.m 核心的Native端实现。
- JSpatch.js 核心的js端实现。
- Extensions 扩展的方法提供给js端调用,内部是OC实现。
- Loader 一套热补丁动态更新补丁脚本的客户端实现,需要配合服务端才能实现整个更新框架。(本文不对此作多赘述)
4.调试分析
4.1调用代码
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
4.2 JPEngine初始化
[JPEngine startEngine];
我们来看看具体的代码实现,
- 代码片段一:
+ (void)startEngine
{
if (![JSContext class] || _context) {
return;
}
JSContext *context = [[JSContext alloc] init];
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);
};
/*.... 分块分析,暂时省略以下代码*/
}
_content 为JSContect的实例,根据苹果官方文档 :
我们知道JSContext为js的执行环境。因此例如:
context[@"_OC_defineProtocol"]=block实现
这样的调用就很好的理解为为js的上下文注入了全局的_OC_defineProtocol
方法,而具体的实现对应着Native端的block的实现。
- 代码片段二:
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"JSPatch" ofType:@"js"];
NSAssert(path, @"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];
}
加载核心的JSPatch.js代码完成初始化,具体js代码后续具体调用再作分析。
4.3具体修复代码
我们来看看Demo的修复代码:
defineClass('JPViewController', {
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)
}
})
调试看看执行步骤:
4.3.1 首先执行global.defineClass (js端)
global.defineClass = function(declaration, properties, instMethods, clsMethods)
我们断点到var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
处,查看局部变量表
newInstMethods newClsMethods
均被赋值,且其中每个实例或类方法的js对象被修改添加参数的个数的说明,只是好奇为什么需要加入参数个数的说明呢???
4.3.2 执行_OC_defineClass
(js端 -> Native端)
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
实际执行的是Native的 static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
方法,以下分代码片段解析。
- 代码片段一:
NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];
NSString *className;
NSString *superClassName;
NSString *protocolNames;
[scanner scanUpToString:@":" intoString:&className];
if (!scanner.isAtEnd) {
scanner.scanLocation = scanner.scanLocation + 1;
[scanner scanUpToString:@"<" intoString:&superClassName];
if (!scanner.isAtEnd) {
scanner.scanLocation = scanner.scanLocation + 1;
[scanner scanUpToString:@">" intoString:&protocolNames];
}
}
if (!superClassName) superClassName = @"NSObject";
className = trim(className);
superClassName = trim(superClassName);
NSArray *protocols = [protocolNames length] ? [protocolNames componentsSeparatedByString:@","] : nil;
Class cls = NSClassFromString(className);
if (!cls) {
Class superCls = NSClassFromString(superClassName);
if (!superCls) {
NSCAssert(NO, @"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);
}
}
通过传递的classDeclaration解析相应的className,superClassName,protocols,
//1.
cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
objc_registerClassPair(cls);
//2.
if (protocols.count > 0) {
for (NSString* protocolName in protocols) {
Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]);
class_addProtocol (cls, protocol);
}
}
1.运行期间创建一个新类,并完成注册. 2.遍历协议名,依此初始化并完成对类的协议的添加。
- 代码片段二:
for (int i = 0; i < 2; i ++) {
BOOL isInstance = i == 0;
JSValue *jsMethods = isInstance ? instanceMethods: classMethods;
Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);
NSDictionary *methodDict = [jsMethods toDictionary];
for (NSString *jsMethodName in methodDict.allKeys) {
JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
int numberOfArg = [jsMethodArr[0] toInt32];
NSString *selectorName = convertJPSelectorString(jsMethodName);
if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {
selectorName = [selectorName stringByAppendingString:@":"];
}
JSValue *jsMethod = jsMethodArr[1];
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]);
}
}
}
}
}
-
遍历传递过过来的实例方法,类方法的js实例,然后依次遍历方法字典,完成方法名js命名到native命名的转换。
-
通过转换后的方法名,用
class_respondsToSelector
判断是否该类的方法列表中是否已经存在该方法的实现,存在即调用overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);
覆盖方法实现。 -
如果类的方法列表中不存在该方法的实现,则通过实现的协议的列表查找,依次判断方法是否在协议的实现方法中,如果以上都不是说明是新添加的方法.
-
代码片段三:
#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
添加类的getProp:
, setProp:forKey:
的方法及实现。
- 代码片段四:
static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
{
SEL selector = NSSelectorFromString(selectorName);
if (!typeDescription) {
Method method = class_getInstanceMethod(cls, selector);
typeDescription = (char *)method_getTypeEncoding(method);
}
IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;
IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
if (typeDescription[0] == '{') {
//In some cases that returns struct, we should use the '_stret' API:
//http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
//NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription.
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:typeDescription];
if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
}
#endif
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
}
#pragma clang diagnostic pop
if (class_respondsToSelector(cls, selector)) {
NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName];
SEL originalSelector = NSSelectorFromString(originalSelectorName);
if(!class_respondsToSelector(cls, originalSelector)) {
class_addMethod(cls, originalSelector, originalImp, typeDescription);
}
}
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
SEL JPSelector = NSSelectorFromString(JPSelectorName);
_initJPOverideMethods(cls);
_JSOverideMethods[cls][JPSelectorName] = function;
class_addMethod(cls, JPSelector, msgForwardIMP, typeDescription);
}
核心的替换添加方法实现的方法:
- 把原始的方法的实现替换为
_objc_msgForward
,即该方法的调用会走消息转发的路径. - 类的
forwardInvocation:
方法的实现被替换为JPForwardInvocation
的实现 - 添加的方法
ORIGforwardInvocation
指向原始的实现IMP. - 添加的方法
ORIG+selector
指向原始的实现的IMP. - 添加
_JP+selector
指向新的函数的实现.
</br>
上述代码只是讲解了替换添加方法的实现,而新的实现方法是一个个js的对象,如何关联到Native端的调用?后续会继续更新,分析 调用核心方法JPForwardInvocation,JPExtension等其他部分的实现,对于上述的分析有问题处欢迎及时指出,谢谢!
补充:4.3.1 中为什么需要加入参数个数的说明呢,与方法的签名有关,具体下次更新作分析!!!
本人还在不断的学习积累中,有问题欢迎及时指出,谢谢!
网友评论