上篇文章简单分析了修复部分的代码实现,本文直接开始由调用过程入手。
而调用的入口就是消息转发实现方法JPForwardInvocation
,下面将由此方法入手开始分析。
1.调用过程
1.1 JPForwardInvocation
1.1.1 入口
还是以上篇文章的handleBtn
方法作为例子阐述整个的调用过程。
当点击模拟器的Push JPTableViewController
按钮时,handleBtn
的方法被调用,由上篇文章4.3.2中以下代码我们已经知道selector
的实现实际走消息转发的流程。
IMP msgForwardIMP = _objc_msgForward;
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);
同样4.3.2中的以下代码我们知道消息转发的实现已经替换为静态方法JPForwardInvocation
的具体实现,因此下面我们具体看看这里的实现。
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.2 实现
代码片段一:
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是否在该类中已经实现,否则就走原始方法的消息转发的流程。
</br>
代码片段二:
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与相应的参数都添加到一个集合中。
</br>
代码片段三:
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
方法,执行函数。
</br>
上篇文章4.3.2部分我们已经知道handleBtn
的实现部分实际上是_JPhandleBtn
对应的方法的js函数实现,而此时我们有疑问,具体js函数的替换实现(见代码)是如何执行的呢?下面我们将分析下一个核心方法callSelector
。
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详解
Object.defineProperty(Object.prototype, "__c", {value: function(methodName)
{
...
}, configurable:false, enumerable: false});
因此我们只需要关注__c
方法的具体实现,分析发现它的核心实现是
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
}
查看_methodFunc
的代码,最终定位_OC_callI
,_OC_callC
两个方法
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
的入口,下面主要分析它的具体实现。
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 实现
代码片段一:
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对象
</br>
代码片段二:
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;
}
</br>
判断是否是父类的方法,走父类的方法的实的实现
代码片段三:
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 中为什么需要加入参数个数的说明呢?
如下代码:
for (int i = 0; i < numberOfArg; i ++) {
[typeDescStr appendString:@"@"];
}
overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);**
需要根据传递过来的参数的个数生成方法的签名。
JSPatch核心的代码分析的部分已经完成,可以参考我的两篇博文,
JSPatch源码学习(一)
JSPatch源码学习(二)部分细节问题未作具体的分析,例如内存,JPBoxing
,JPExtension
等,有兴趣可以关注我后期的该主题的博文。
本人还在不断的学习积累中,有问题欢迎及时指出,谢谢!