原理

JSPatch 能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言,OC 上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法。

执行过程

当客户端从服务器下载一段JS代码后: 1.客户端调用[JPEngine StartEngine],这时会创建一个JSContext *_context,然后给_context创造各种方法:

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);
};
context[@"_OC_formatJSToOC"] = ^id(JSValue *obj) {
return formatJSToOC(obj);
};
context[@"_OC_formatOCToJS"] = ^id(JSValue *obj) {
return formatOCToJS([obj toObject]);
};......
复制代码

这些方法会通过JavaScriptCore暴露给JS进行调用。 2.进行完_context初始化后,会通过_context执行JSPatch自带的JS语句,进行JS环境下的全局变量的初始化,比如将JS中方法,参数转成OC格式语句的核心方法:

//_methodFunc是JS调用OC的入口函数
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
var selectorName = methodName
//判断是否是直接调用performSelector,若不是则进行方法名加工
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)
//将OC执行后的返回值转化为JS格式
return _formatOCToJS(ret)
}
复制代码

还有JS中所有调用方法的元函数__c:

__c: function(methodName) {
var slf = thisif (slf instanceof Boolean) {
return function() {
return false
}
}
if (slf[methodName]) {
//将从prototype获取的函数指定绑定作用域
return slf[methodName].bind(slf);
}if (!slf.__obj && !slf.__clsName) {
throw new Error(slf + '.' + methodName + ' is undefined')
}
if (slf.__isSuper && slf.__clsName) {
slf.__clsName = _OC_superClsName(slf.__obj.__realClsName ? slf.__obj.__realClsName: slf.__clsName);
}
var clsName = slf.__clsName
//假如是下发JS中存在的方法,直接返回JS中的方法
if (clsName && _ocCls[clsName]) {
var methodType = slf.__obj ? 'instMethods': 'clsMethods'
if (_ocCls[clsName][methodType][methodName]) {
slf.__isSuper = 0;
return _ocCls[clsName][methodType][methodName].bind(slf)
}
}
//否则返回一个匿名函数,匿名函数的返回值作为最终的返回值进行接下来的JS方法调用
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
}
}
复制代码

还有其他的全局变量声明,只把这两个最核心的函数进行展示,就不一一赘述了。

3.进行完所有前期工作后,接下来就是进行下发JS语句的执行,首先利用正则表达式进行JS中的方法替换,比如var tableViewCtrl = JPTableViewController.alloc().init()会转化成 var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")()

4.接下来就会通过[_context evaluateScript:script withSourceURL:[NSURL URLWithString:@"JSPatch.js"]];执行替换后的代码。 调用global.defineClass = function(declaration, properties, instMethods, clsMethods) {} 这个函数内部会判断properties是不是数组

if (!(properties instanceof Array)) {
clsMethods = instMethods
instMethods = properties
properties = null
}
复制代码

然后调用_formatDefineMethods(instMethods, newInstMethods, realClsName),这个函数主要是构建之前声明的newInstMethodsnewClsMethods:

newMethods : {
method1: [function(param1,param2...).length ,function(){}],
method2: [function(param1,param2...).length ,function(){}]
}
复制代码

数组内的function是匿名函数(1),如下:

function() {
try {
var args = _formatOCToJS(Array.prototype.slice.call(arguments))
var lastSelf = global.self
global.self = args[0]
if (global.self) global.self.__realClsName = realClsName
args.splice(0,1)
//调用原始的JS方法
var ret = originMethod.apply(originMethod, args)
global.self = lastSelf
return ret
} catch(e) {
_OC_catch(e.message, e.stack)
}
}
复制代码

然后通过_OC_defineClassdeclarationnewInstMethodsnewClsMethods暴露给OC。

var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
复制代码

最终在OC中调用defineClass,将JS中传过来的classDeclaration进行解析,得到当前类和父类以及协议。 当没有找到当前类时,就在父类中注册一个新当前类。给当前类添加完协议,将覆盖或者新建的方法通过runtime将imp指针统一替换成JPForwardInvocation的函数指针。 当在OC中调用JS方法会通过JPForwardInvocation,给这个匿名函数传入OC的参数,获取原始JS函数的返回值。

if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
//class_replaceMethod这个方法交换不存在的方法时会等同于addMethod,返回nil
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
if (originalForwardImp) {
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
}
}
复制代码

利用runtime进行方法添加或交换imp然后调用overrideMethod,将上述的匿名函数(1)保存到_JSOverideMethods。 当defineClass在OC环境中执行完毕后,会将Class和SuperClass返回到JS环境下,为了在JS环境中模仿子类能都调用父类的方法,会将父类中被JS重写的方法会同样保存在子类中。而所有重写类的所有类方法和实例方法都放在_ocCls中,大致结构是

_ocCls : {
className: {
instMethods: {
//这里的function就是下发的脚本中对应的方法对应的js函数
methodName: function(){},
methodName1: function(){}
},
clsMethods: {
methodName: function(){},
methodName1: function(){}
},
},
className1: {
instMethods: {
methodName: function(){},
methodName1: function(){}
},
clsMethods: {
methodName: function(){},
methodName1: function(){}
},
}
}
复制代码

在JS中调用元函数时,会判断_ocCls中是否存在对应的方法,若有,就调用_ocCls[clsName][methodType][methodName].bind(slf),将调用者slf通过bind传到对应函数中,等待__c调用。

到此,在执行完下载的脚本后,所需覆盖的OC方法的imp指针已经指向了JPForwardInvocation

static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
{
//精简前面部分代码
if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
//1.替换当前类的forwardInvocation为JPForwardInvocation
IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
if (originalForwardImp) {
class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
}}[cls jp_fixMethodSignature];
if (class_respondsToSelector(cls, selector)) {
//2.假如selector有实现,则添加一个新的Selector指向原始的imp,这时候原始的seletor也是指向原始的imp
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];_initJPOverideMethods(cls);
_JSOverideMethods[cls][JPSelectorName] = function;//3.在最后才进行原始selector的imp替换,防止有可能的调用overrideMethod时同时调用这个方法导致的多线程问题。
//再将原始的Selector指向原始的_objc_msgForward,这样只要在外界调用这个selector直接跳过imp查找,
//先后执行resolveInstanceMethod,forwardingTargetForSelector,methodSignatureForSelector,forwardInvocation
//当在调用forwardInvocation就会跳到JPForwardInvocation
class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);}
复制代码

到此,所有前期配置都已完成,接下来就是等待覆写的方法被调用时,触发JPForwardInvocation,进行OC和JS的来回传值。

static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
{
BOOL deallocFlag = NO;
id slf = assignSlf;
NSMethodSignature *methodSignature = [invocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];NSString *selectorName = NSStringFromSelector(invocation.selector);
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
//在_JSOverideMethods中获取之前保存的JSFunc,用于调用原始的JS函数
JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);
if (!jsFunc) {
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}
//下面是将参数传入到JS的代码
//将调用者信息保存到argList
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]];
}
//methodSignatured的前两个参数固定是调用者和seletor
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
//将参数依次取出,判断类型,添加到arglist
case '@': {
__unsafe_unretained id arg;
[invocation getArgument:&arg atIndex:i];
if ([arg isKindOfClass:NSClassFromString(@"NSBlock")]) {
[argList addObject:(arg ? [arg copy]: _nilObj)];
} else {
[argList addObject:(arg ? arg: _nilObj)];
}
break;
}
default: {
NSLog(@"error type %s", argumentType);
break;
}
}
}//将普通对象数组转换为字典对象数组,里面有参数的实例和类名 @[{__obj:obj,__cls:NSObject},{__obj:obj2,__cls:NSObject}]
NSArray *params = _formatOCToJSList(argList);
//下面是处理返回值类型传入到JS
char returnType[255];
strcpy(returnType, [methodSignature methodReturnType]);// Restore the return type
if (strcmp(returnType, @encode(JPDouble)) == 0) {
strcpy(returnType, @encode(double));
}
if (strcmp(returnType, @encode(JPFloat)) == 0) {
strcpy(returnType, @encode(float));
}
//当调用实例方法,且参数是id类型时调用顺序: jsFunc->originJSMethod->__c->_methodFunc->_OC_callI->callSelector->获取返回值,一步步回传到jsVal
//如此就完成了JS和OC的通信,整个过程是串行执行的。switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
#define JP_FWD_RET_CALL_JS \
JSValue *jsval; \
[_JSMethodForwardCallLock lock];   \
jsval = [jsFunc callWithArguments:params]; \
[_JSMethodForwardCallLock unlock]; \
case 'v': {
JP_FWD_RET_CALL_JS
break;
}
default: {
break;
}
}if (_pointersToRelease) {
for (NSValue *val in _pointersToRelease) {
void *pointer = NULL;
[val getValue:&pointer];
CFRelease(pointer);
}
_pointersToRelease = nil;
}if (deallocFlag) {
slf = nil;
Class instClass = object_getClass(assignSlf);
Method deallocMethod = class_getInstanceMethod(instClass, NSSelectorFromString(@"ORIGdealloc"));
void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
originalDealloc(assignSlf, NSSelectorFromString(@"dealloc"));
}
}
复制代码

关于block传递

当JSPatch扫描下发脚本时,会将所有的block标记,还是以调用实例方法为例,当调用链走到callSelector时,对JS参数进行解析的时候,假如发现有block

if ([(JSValue *)arguments[i-2] hasProperty:@"__isBlock"]) {
JSValue *blkJSVal = arguments[i-2];
Class JPBlockClass = NSClassFromString(@"JPBlock");
if (JPBlockClass && ![blkJSVal[@"blockObj"] isUndefined]) {
__autoreleasing id cb = [JPBlockClass performSelector:@selector(blockWithBlockObj:) withObject:[blkJSVal[@"blockObj"] toObject]];
[invocation setArgument:&cb atIndex:i];
Block_release((__bridge void *)cb);
} else {
__autoreleasing id cb = genCallbackBlock(arguments[i-2]);
[invocation setArgument:&cb atIndex:i];
}
}
复制代码

将传入的block通过libffi生成一个函数指针,替换invocation原来的参数。

JSPatch源码解读相关推荐

  1. Bert系列(二)——源码解读之模型主体

    本篇文章主要是解读模型主体代码modeling.py.在阅读这篇文章之前希望读者们对bert的相关理论有一定的了解,尤其是transformer的结构原理,网上的资料很多,本文内容对原理部分就不做过多 ...

  2. Bert系列(三)——源码解读之Pre-train

    https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...

  3. linux下free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  4. nodeJS之eventproxy源码解读

    1.源码缩影 !(function (name, definition) { var hasDefine = typeof define === 'function', //检查上下文环境是否为AMD ...

  5. PyTorch 源码解读之即时编译篇

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者丨OpenMMLab 来源丨https://zhuanlan.zhihu.com/ ...

  6. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

  7. Feflow 源码解读

    Feflow 源码解读 Feflow(Front-end flow)是腾讯IVWEB团队的前端工程化解决方案,致力于改善多类型项目的开发流程中的规范和非业务相关的问题,可以让开发者将绝大部分精力集中在 ...

  8. spring-session源码解读 sesion

    2019独角兽企业重金招聘Python工程师标准>>> spring-session源码解读 sesion 博客分类: java spring 摘要: session通用策略 Ses ...

  9. 前端日报-20160527-underscore 源码解读

    underscore 源码解读 API文档浏览器 JavaScript 中加号操作符细节 抛弃 jQuery,拥抱原生 JS 从 0 开始学习 GitHub 系列之「加入 GitHub」 js实现克隆 ...

最新文章

  1. ios11修改微信步数_一个人有多孤独,看他的微信步数就知道了
  2. 安装百分之80卡住_新车买回来要不要安装发动机护板呢?装好还是不好?
  3. 在python中使用什么函数进行输出_Python中使用pprint函数进行格式化输出的教程
  4. 用模板类实现shared_ptr和unique_ptr
  5. url参数拼接 php,js URL参数的拼接方法比较_javascript技巧
  6. Centos 源码安装zabbix 2.4.5
  7. Spring注解扫描原理浅析
  8. 简历关于计算机办公软件怎么写,简历中怎么写办公软件
  9. Python全栈自动化测试--Pycharm专业版安装
  10. 十年Android程序员图解:用图帮你了解https的原理
  11. JavaScript中加号运算符+ 运算过程理解
  12. 数据库有哪些类型?如何根据应用场景选择?终于有人讲明白了
  13. python 运行 daemon 程序
  14. 软件工程师眼睛疲劳咋办啊_如何避免计算机眼睛疲劳并保持眼睛健康
  15. html简单个人网页制作 HTML5+CSS大作业——程序员个人简历设计(5页)
  16. 关于软件工程----一线城市与二三线城市的区别
  17. Next '21 大会倒计时 1 天丨与 Google Cloud 一起寻找打开数字化的“云钥匙”
  18. OOV问题-论文笔记《Neural Machine Translation of Rare Words with Subwords Units》- ACL2016
  19. 杰理AC692X---简介(1)
  20. 设计模式-创建型模式(单例、简工、工方)

热门文章

  1. 进阶07 Set接口、HashSet、LinkedHashSet
  2. 3h精通OpenCV(三)-重调大小与图像裁剪
  3. leach协议c++代码_入门教程4:教你STM32F407标准库移植机智云代码(控制LED灯)
  4. 织梦html底部文件,织梦dedecms程序如何给网站底部添加360监控的步骤
  5. 不能使用 float 和 double 来表示金额等精确的值
  6. 云丁智能锁使用说明书_出门不再带钥匙 云丁D2F智能指纹锁新体验
  7. javascript权威指南_重读javascript权威指南(3)
  8. 600分左右的计算机院校,600分左右的985大学 性价比最高的学校
  9. php 自定义条件,php如何自定义一个方法
  10. mysql jdbc官方,mysql_jdbc