上篇文章簡單分析了修復部分的代碼實現,本文直接開始由調用過程入手。
而調用的入口就是消息轉發實現方法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
等,有興趣可以關注我后期的該主題的博文。
本人還在不斷的學習積累中,有問題歡迎及時指出,謝謝!