JSPatch 源码分析(二)

上篇文章简单分析了修复部分的代码实现,本文直接开始由调用过程入手。
而调用的入口就是消息转发实现方法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等,有兴趣可以关注我后期的该主题的博文。

本人还在不断的学习积累中,有问题欢迎及时指出,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容