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端定义的函数存在以下问题:

  1. 要为js端定义函数替换或添加objc的函数,那么此时函数的签名的处理

  2. 调用函数时参数的获取

  3. 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的意义

  1. 为了在OC端调替换方法时,能够处理方法参数的个数

  2. 处理oc调用js端时能够正常调用到jspatch上定义的js方法

    1. 处理oc传来的参数为js

    2. 处理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源码解析的主要内容,如果未能解决你的问题,请参考以下文章

全面谈谈Aspects和JSPatch兼容问题

全面谈谈Aspects和JSPatch兼容问题

正确姿势介入JSPatch

JSPatch解析

JSPatch VS Wax

Weex & ReactNative & JSPatch