JSPatch源码解析
Posted 攻城狮杂谈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSPatch源码解析相关的知识,希望对你有一定的参考价值。
JSPatchDemo中demo.js
defineClass('JPViewController', {
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)
}
})
注入JSCore中变成了
defineClass('JPViewController', {
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.__c("alloc")().__c("init")()
self.__c("navigationController")().__c("pushViewController_animated")(tableViewCtrl, YES)
}
})
oc的函数调用被转成了__c("")的函数调用形式,根据JSPatch Wiki所言这是为了实现oc的函数调用链。
Q:为什么采用把js函数调用转成__c("")的形式调用呢?这样会有什么问题吗?
A: 这样做是为了实现对象的继承链,作者最开始采用为每个对象添加相应的方法,结果发现占用很大的内存,后来在JS上实现了一套继承链,仍然很大,采用__c("")进行一次转发,把不能处理的消息全都转发到Objc处理。
这样做的问题是,js原本的函数(e.g. Array.prototype.push()等)方法也转正了__c("")去调用,所以在转发的时候需要去单独处理这种情况
var _customMethods = {
__c: function(methodName) {
var slf = this
if (slf instanceof Boolean) {
return function() {
return false
}
}
// 由于默认把js的函数调用都转成了__c("methodName")形式
// 所以这里判断js对象是否为原本有的方法,若是则调用js原本方法
// 采用bind的形式调用函数相当于把函数内的this设置为bind的对象
if (slf[methodName]) {
return slf[methodName].bind(slf);
}
if (!slf.__obj && !slf.__clsName) {
throw new Error(slf + '.' + methodName + ' is undefined')
}
if (slf.__isSuper && slf.__clsName) {
// 获取父类className
slf.__clsName = _OC_superClsName(slf.__obj.__realClsName ? slf.__obj.__realClsName: slf.__clsName);
}
var clsName = slf.__clsName
// 若是在js中定义的类OC函数,则走这里调用
if (clsName && _ocCls[clsName]) {
// 从已经被存储js函数中取相应的函数执行
var methodType = slf.__obj ? 'instMethods': 'clsMethods'
if (_ocCls[clsName][methodType][methodName]) {
slf.__isSuper = 0;
return _ocCls[clsName][methodType][methodName].bind(slf)
}
}
// 否则认为是OC的原生函数,走这个方法转发到OC中调用
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
}
},
super: function() {
// 处理super调用
var slf = this
if (slf.__obj) {
slf.__obj.__realClsName = slf.__realClsName;
}
return {__obj: slf.__obj, __clsName: slf.__clsName, __isSuper: 1}
},
}
for (var method in _customMethods) {
// 采用 Object.hasOwnProperty方法可以判断,某个方法是定义的属性
// 而非从原型链中继承的属性
if (_customMethods.hasOwnProperty(method)) {
// 为Object定义了一个method的属性
// 定义语法可见MDN https://developer.mozilla.org/en-US/docs/Web/javascript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false})
}
}
2. 定义class、protocol,增加属性替换
global.defineClass = function(declaration, properties, instMethods, clsMethods) {
var newInstMethods = {}, newClsMethods = {}
// 只传三个参数时,重新设置相应的参数
if (!(properties instanceof Array)) {
clsMethods = instMethods
instMethods = properties
properties = null
}
if (properties) {
// 处理属性
properties.forEach(function(name){
if (!instMethods[name]) {
instMethods[name] = _propertiesGetFun(name);
}
var nameOfSet = "set"+ name.substr(0,1).toUpperCase() + name.substr(1);
if (!instMethods[nameOfSet]) {
instMethods[nameOfSet] = _propertiesSetFun(name);
}
});
}
var realClsName = declaration.split(':')[0].trim()
// 格式化函数,处理为[args_numbers, function()],稍后再讲
_formatDefineMethods(instMethods, newInstMethods, realClsName)
_formatDefineMethods(clsMethods, newClsMethods, realClsName)
// 在OC侧替换或添加方法
var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
var className = ret['cls']
var superCls = ret['superCls']
_ocCls[className] = {
instMethods: {},
clsMethods: {},
}
// 如果是在父类的JS上定义的方法,则添加到当前类上
if (superCls.length && _ocCls[superCls]) {
for (var funcName in _ocCls[superCls]['instMethods']) {
_ocCls[className]['instMethods'][funcName] = _ocCls[superCls]['instMethods'][funcName]
}
for (var funcName in _ocCls[superCls]['clsMethods']) {
_ocCls[className]['clsMethods'][funcName] = _ocCls[superCls]['clsMethods'][funcName]
}
}
// 添加当前类定义的方法
_setupJSMethod(className, instMethods, 1, realClsName)
_setupJSMethod(className, clsMethods, 0, realClsName)
return require(className)
}
JSPatch通过解析js上的类的定义,获取类名,父类名,protocol名,并在Objc端获取相应类,对于不存在的类会通过objc runtime创建一个类,并添加相应方法,否则直接对于该类添加或替换方法
要正常调用js端定义的函数存在以下问题:
要为js端定义函数替换或添加objc的函数,那么此时函数的签名的处理
调用函数时参数的获取
self关键字处理
函数签名的处理:
js端函数定义的形式为闭包,Objc端处理闭包的属性名把"_"替换为":",为类添加或替换相应的selector,代码可见convertJPSelectorString(),在js传递到objc时,通过_formatDefineMethods(),把函数的参数个数传递类过来
objc被替换被调用时参数的获取
将替换方法的实现objc_msgForward(),在转发的时候,通过NSInvocation获取参数,通过-[JSValue callWithArguments:]调用js函数
static void JPForwardInvocation(__attribute__((objc_ownership(none))) id assignSlf, SEL selector, NSInvocation *invocation)
{
switch()
{
...
case '@' : {
JSValue * jsval;
[_JSMethodForwardCallLock lock];
jsval = [jsFunc callWithArguments:params];
[_JSMethodForwardCallLock unlock];
while (! [jsval isNull] && ![jsval isUndefined] && [jsval hasProperty: @"__isPerformInOC"]) {
NSArray * args = ((void * ) 0);
JSValue * cb = jsval[@"cb"];
if ([jsval hasProperty: @"sel"]) {
id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : ((void * ) 0), [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : ((void * ) 0), __objc_no);
args = @ [[_context[@"_formatOCToJS"] callWithArguments: callRet ? @ [callRet] : _formatOCToJSList(@ [_nilObj])]];
} [_JSMethodForwardCallLock lock];
jsval = [cb callWithArguments: args];
[_JSMethodForwardCallLock unlock];
}
id __attribute__((objc_ownership(autoreleasing))) ret = formatJSToOC(jsval);
if (ret == _nilObj || ([ret isKindOfClass: [NSNumber class]] && strcmp([ret objCType], "c") == 0 && ![ret boolValue]))
ret = ((void * ) 0); [invocation setReturnValue: &ret];
break;
}
...
default: {
break;
}
}
...
}
self关键字通过在js侧定义一个全局对象实现
_formatDefineMethods的意义
为了在OC端调替换方法时,能够处理方法参数的个数
处理oc调用js端时能够正常调用到jspatch上定义的js方法
处理oc传来的参数为js
处理self关键字
var _formatDefineMethods = function(methods, newMethods, realClsName) {
for (var methodName in methods) {
if (!(methods[methodName] instanceof Function)) return;
(function(){
var originMethod = methods[methodName]
// Function.length是获取js函数的预期被传入参数数量
// 后面的js闭包,处理oc调用js的参数转换和self关键字处理
newMethods[methodName] = [originMethod.length, 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)
var ret = originMethod.apply(originMethod, args)
global.self = lastSelf
return ret
} catch(e) {
_OC_catch(e.message, e.stack)
}
}]
})()
}
}
以上是关于JSPatch源码解析的主要内容,如果未能解决你的问题,请参考以下文章