JSPatch的基本原理:JS传递字符串给OC,OC通过Runtime接口调用和替换OC方法。
方法调用
1. require实现
var _require = function(clsName) {
if (!global[clsName]) {
global[clsName] = {
// _clsName表示这是一个OC的对象
__clsName: clsName
}
}
return global[clsName]
}
global.require = function(clsNames) {
var lastRequire
clsNames.split(',').forEach(function(clsName) {
lastRequire = _require(clsName.trim())
})
return lastRequire
}
通过这个方法可以看到requre(‘UIView’)这个函数只做了什么,在全局作用域上声明一个对应的变量 UIView,它是类型是一个对象,这个对象有个属性 __clsName
就是’UIView’。
通过clsNames.split(‘,’).foreach这行代码,我们可以看到require一次包含多个对象是如何实现的。
2. JS接口
由于JS不存在OC/ruby那样的消息转发机制,JSPatch作者做了一个很有意思的处理,对JS脚本做个简单编译。将所有JS的方法调用都通过正则进行替换,统一调用 __c()
函数,然后再进行分发。例如下面的转换:
UIView.alloc().init()
–> UIView.__c('alloc')().__c('init')()
下面我们可以看一下 __c()
需要返回一个function,然后这个function会被调用,下面是__c
元函数的源代码
// 为Object的原型定义一个称之为__c的属性,它的值是返回一个function返回值的function,并且设置这个属性configurable:false, enumerable: false。
// JS通过这种为prototype原型添加属性的方式,为所有的JS对象都添加了这个方法。JS对象调用__c方法的时候,通过调用链就可以找到这个方法。
Object.defineProperty(Object.prototype, "__c", {value: function(methodName) {
// ??
if (this instanceof Boolean) {
return function() {
return false
}
}
// 如果对象的__obj和__clsName都是空,表明这是一个JS对象,在对象中查找并返回对应的方法即可
if (!this.__obj && !this.__clsName) {
// 如果在这个JS对象中根本没有这个方法,抛出异常
if (!this[methodName]) {
throw new Error(this + '.' + methodName + ' is undefined')
}
// 调用bind(this)跟JS的语法特性有关系,绑定返回的这个方法的上下文执行环境为这个对象自身,否则调用的时候,方法内部的this会指向其他离它最近的一个执行环境
return this[methodName].bind(this);
}
// 这里使用self变量保存this的原因是,如果我们直接返回这个function,在里面使用this,指向的就不是当前调用这个函数的对象,所以需要使用self保持this,当然也可以使用bind
var self = this
// 在调用super的时候,做了一个特殊处理
if (methodName == 'super') {
return function() {
if (self.__obj) {
self.__obj.__clsDeclaration = self.__clsDeclaration;
}
// 返回一个新的对象,这个对象的__isSuper标识等于1,在OC调用执行的时候,会判断这个标识来决定执行哪一个方法
return {__obj: self.__obj, __clsName: self.__clsName, __isSuper: 1}
}
}
/**
* _methodFunc的作用是把JS的相关调用信息传递给OC,然后OC通过runtime来执行这些调用,返回结果
*/
if (methodName.indexOf('performSelector') > -1) {
if (methodName == 'performSelector') {
// return的这个function的作用是:直接调用OC对应的SEL,而不是通过OC调用performSelector,与最下面那个直接调用OC方法的function不同的是关于参数的处理
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, args[0], args.splice(1), self.__isSuper, true)
}
} else if (methodName == 'performSelectorInOC') {
// 这个与上面的区别在于,这里的调用是异步,为了防止出现线程竞争问题,调用完毕之后进行callback回调
// refer: https://github.com/bang590/JSPatch/wiki/performSelectorInOC-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3
return function(){
var args = Array.prototype.slice.call(arguments)
return {__isPerformInOC:1, obj:self.__obj, clsName:self.__clsName, sel: args[0], args: args[1], cb: args[2]}
}
}
}
// return的这个function的作用是:处理参数,通过_methodFunc进行OC调用,然后返回执行结果
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
}
}, configurable:false, enumerable: false})
3. 消息传递
JS调用OC函数时,JavaScriptCore会自动会参数和返回值进行转换。基本类型会自动进行转换,详细的转换参考JavaScriptCore的API
通过上面的讲解我们知道,JSPatch主要是通过_methodFunc
方法调用OC的消息