JSPatch源码解析(二)

上篇文章简单分析了修复部分的代码实现,本文直接开始由调用过程入手。

调用过程

1.1 JPForwardInvocation

1.1.2 入口

还是以上篇文章的handleBtn方法作为例子阐述整个的调用过程。

当点击模拟器的Push JPTableViewController按钮时,handleBtn的方法被调用,由上篇文章4.3.2中以下代码我们已经知道selector的实现实际走消息转发的流程。

1
2
IMP msgForwardIMP = _objc_msgForward;
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);

同样4.3.2中的以下代码我们知道消息转发的实现已经替换为静态方法JPForwardInvocation的具体实现,因此下面我们具体看看这里的实现。

1
2
3
4
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@:@");
}

1.1.1 实现

代码片段一:

1
2
3
4
5
6
7
8
9
10
11
12
id slf = assignSlf;
NSMethodSignature *methodSignature = [invocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];

NSString *selectorName = NSStringFromSelector(invocation.selector);
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
SEL JPSelector = NSSelectorFromString(JPSelectorName);

if (!class_respondsToSelector(object_getClass(slf), JPSelector)) {
JPExcuteORIGForwardInvocation(slf, selector, invocation);
return;
}

判断新的selector是否在该类中已经实现,否则就走原始方法的消息转发的流程。

代码片段二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSMutableArray *argList = [[NSMutableArray alloc] init];
if ([slf class] == slf) {
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) {
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else {
[argList addObject:[JPBoxing boxWeakObj:slf]];
}
...
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;
}
}

把self与相应的参数都添加到一个集合中。

代码片段三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NSArray *params = _formatOCToJSList(argList);
const char *returnType = [methodSignature methodReturnType];
...

#define JP_FWD_RET_CALL_JS \
JSValue *fun = getJSFunctionInObjectHierachy(slf, JPSelectorName); \
JSValue *jsval; \
[_JSMethodForwardCallLock lock]; \
jsval = [fun callWithArguments:params]; \
[_JSMethodForwardCallLock unlock]; \
while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) { \
NSArray *args = nil; \
JSValue *cb = jsval[@"cb"]; \
if ([jsval hasProperty:@"sel"]) { \
id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO); \
args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]]; \
} \
[_JSMethodForwardCallLock lock]; \
jsval = [cb callWithArguments:args]; \
[_JSMethodForwardCallLock unlock]; \
}

把包含self与调用的参数转换为js对象,getJSFunctionInObjectHierachy获取对应的js重写的函数,直接调用callWithArgument方法,执行函数。

以上部分我们发现handleBtn的实现部分实际上是_JPhandleBtn对应的方法的实现,调用的流程基本了解,而此时我们有疑问,Demo中handleBtn具体的替换实现(见代码)是如何执行的呢?

1
2
var tableViewCtrl = JPTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)

1.2 callSelector

1.2.1 入口

代码片段一:分析JSPatch.js的代码部分时我们发现会有如下一段代码,给js对象基类 Object 的 prototype 加上 c 成员,这样所有对象都可以调用到 c,为什么这么做可以查看原作者
wiki详解

1
2
3
4
Object.defineProperty(Object.prototype, "__c", {value: function(methodName) 
{
...
}, configurable:false, enumerable: false});

因此我们只需要关注__c方法的具体实现,分析发现它的核心实现是

1
2
3
4
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
}

查看_methodFunc的代码,最终定位_OC_callI,_OC_callC两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
if (!isPerformSelector) {
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)
return _formatOCToJS(ret)
}

由startEngine可知,_OC_callI,_OC_callC两个方法为注入到context的全局的方法,因此就定位到callSelector。以上分析了callSelector的入口,下面主要分析它的具体实现。

1
2
3
4
5
6
context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {
return callSelector(nil, selectorName, arguments, obj, isSuper);
};
context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) {
return callSelector(className, selectorName, arguments, nil, NO);
};

1.2.2 实现

代码片段一:

1
2
3
4
5
6
7
8
9
10
11
if (instance) {
instance = formatJSToOC(instance);
if (!instance || instance == _nilObj) return @{@"__isNil": @(YES)};
}
id argumentsObj = formatJSToOC(arguments);

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

把js对象与参数转换为OC对象

代码片段二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (isSuper) {
NSString *superSelectorName = [NSString stringWithFormat:@"SUPER_%@", selectorName];
SEL superSelector = NSSelectorFromString(superSelectorName);

Class superCls;
if (clsDeclaration.length) {
NSDictionary *declarationDict = convertJPDeclarationString(clsDeclaration);
NSString *defineClsName = declarationDict[@"className"];

Class defineClass = NSClassFromString(defineClsName);
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;
}



判断是否是父类的方法,走父类的方法的实的实现

代码片段三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NSInvocation *invocation;
NSMethodSignature *methodSignature;
if (!_JSMethodSignatureCache) {
_JSMethodSignatureCache = [[NSMutableDictionary alloc]init];
}
if (instance) {
...
invocation= [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:cls];
}
[invocation setSelector:selector];
...
[invocation invoke];
...
return returnValue;

封装NSInvocation并执行,返回处理的结果

补充说明

上一篇博文中预留一个问题:4.3.1 中为什么需要加入参数个数的说明呢?
如下代码:

*typeDescStr = [@"@@:" mutableCopy];
1
for (int i = 0; i < numberOfArg; i ++) {
    [typeDescStr appendString:@"@"];
}
overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);**

需要根据传递过来的参数的个数声称方法的签名。

JSPatch核心的代码分析的部分已经完成,可以参考我的两篇博文,

JSPatch源码学习(一)

JSPatch源码学习(二)
部分细节问题未作具体的分析,例如内存,JPBoxing,JPExtension等,有兴趣可以关注我后期的该主题的博文。本人还在不断的学习积累中,有问题欢迎及时指出,谢谢!