原创:知识探索型文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、HotFix 方案介绍
- 1、Dynamic Framework
- 2、React Native
- 3、JSPatch
- 二、JSPatch 简介
- 1、实际用途
- 2、使用方式
- 3、修复举例
- 4、基本原理
- 5、方案对比
- 6、致命缺陷
- 三、JSPatch的核心原理
- 1、预加载部分
- 2、脚本运行
- 3、require
- 4、defineClass
- 5、对象持有/转换
一、HotFix 方案介绍
相信HotFix
大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结。iOS中的HotFix
方案大致可以分为三种:Dynamic Framework(Apple)
、React Native(Facebook)
、JSPatch(Tencent)
。
1、Dynamic Framework
动态的Framework
,其实就是动态库。首先我介绍一下关于动态库和静态库的一些特性以及区别。不管是静态库还是动态库,本质上都是一种可执行的二进制格式,可以被载入内存中执行。iOS上的静态库可以分为.a
文件和.framework
,动态库可以分为.dylib
(.tdb
)和.framework
。
静态库链接时可以完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。动态库在链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
静态库和动态库是相对编译期和运行期的。静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要修改静态库,而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
总而言之,同一个静态库在不同程序中使用时,每一个程序中都得导入一次,打包时也被打包进去,形成一个程序。而动态库在不同程序中,打包时并没有被打包进去,只在程序运行使用时,才链接载入(如系统的框架如UIKit
、Foundation
等),所以程序体积会小很多。
2、React Native
React Native
支持用JavaScript
进行开发,所以可以通过更改JS
文件实现App的HotFix
,但是这种方案的明显的缺点在于它只适合用于使用了React Native
这种方案的应用。
3、JSPatch
JSPatch
是只需要在项目中引入极小的JSPatch
引擎,就可以使用JavaScript
语言调用Objective-C
的原生接口,获得脚本语言的能力:动态更新iOS APP,替换项目原生代码、快速修复bug
。
二、JSPatch 简介
JSPatch
是一个 iOS 动态更新框架,只需在项目中引入极小的引擎,就可以使用 JavaScript
调用任何 Objective-C
原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug
。
1、实际用途
由于Apple严格的审核标准和低效率,iOS应用的发版速度极慢,稍微大型的app发版基本上都在一个月以上,所以代码热更新(HotfixPatch
)对于iOS应用来说就显得尤其重要。
新版本上线后发现有个严重的bug
,可能会导致crash
率激增,可能会使网络请求无法发出,这时能做的只是赶紧修复bug
然后提交等待漫长的AppStore
审核,再盼望用户快点升级,才能完成此次bug
的修复。
使用JSPatch
可以解决这样的问题,只需在项目中引入JSPatch
,就可以在发现bug
时下发JS
脚本补丁,替换原生方法,无需更新APP即时修复bug
。
2、使用方式
安装框架:
pod 'JSPatch'
OC代码:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.window addSubview:[self genView]];
[self.window makeKeyAndVisible];
return YES;
}
- (UIView *)genView
{
return [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 320)];
}
@end
demo.js
文件中相应部分代码如下:
require('UIView, UIColor, UILabel')
defineClass('AppDelegate', {
// replace the -genView method
genView: function() {
var view = self.ORIGgenView();
view.setBackgroundColor(UIColor.greenColor())
var label = UILabel.alloc().initWithFrame(view.frame());
label.setText("JSPatch");
label.setTextAlignment(1);
view.addSubview(label);
return view;
}
});
3、修复举例
以下代码中取数组元素处可能会超出数组范围导致crash
。
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *content = self.dataSource[[indexPath row]]; //可能会超出数组范围导致crash
JPViewController *ctrl = [[JPViewController alloc] initWithContent:content];
[self.navigationController pushViewController:ctrl];
}
如果在项目里引用了JSPatch
:
#import "AppDelegate.h"
#import "JPEngine.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
.......
}
@end
通过JS
脚本修复这个bug
:
//JS
defineClass("JPTableViewController", {
//instance method definitions
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var row = indexPath.row()
if (self.dataSource().length > row) { //加上判断越界的逻辑
var content = self.dataArr()[row];
var ctrl = JPViewController.alloc().initWithContent(content);
self.navigationController().pushViewController(ctrl);
}
}
}, {})
4、基本原理
JSPatch
用iOS内置的JavaScriptCore.framework
作为JS引擎,但没有用它JSExport
的特性进行JS-OC
函数互调,而是通过Objective-C Runtime
,从JS传递要调用的类名和函数名到Objective-C
,再使用NSInvocation
动态调用对应的OC方法。
JSPatch
能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言,OC 上所有方法的调用/类的生成都通过 Objective-C 的Runtime
在运行时进行。
我们可以通过类名/方法名反射得到相应的类和方法:
Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];
也可以替换某个类的方法为新的实现:
static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"");
还可以新注册一个类,为类添加方法:
Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);
理论上你可以在运行时通过类名/方法名调用到任何 OC 方法,替换任何类的实现以及新增任意类。所以 JSPatch
的基本原理就是:JS 传递字符串给 OC,OC 通过 Runtime
接口调用和替换 OC 方法。这是最基础的原理,实际实现过程还有很多怪要打。
5、方案对比
方案一:Dynamic Framework
不管是静态库还是动态库,本质上都是一种可执行的二进制格式,可以被载入内存中执行。
静态库:
- 链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
- 在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库。
- 以
.a
和.framework
为文件后缀名。
动态库:
- 在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
- 系统只加载一次,多个程序共用,节省内存。
- 以
.tbd
(之前叫.dylib
) 和.framework
为文件后缀名。 - 系统直接提供给我们的
framework
都是动态库。
静态库和动态库是相对编译期和运行期而言的,同一个静态库在不同程序中使用时,每一个程序中都得导入一次,打包时也被打包进去,形成一个程序。而动态库在不同程序中,打包时并没有被打包进去,只在程序运行使用时,才链接载入(如系统的框架如UIKit
、Foundation
等),所以程序体积会小很多。
Dynamic Framework
其实就是我们可以通过更新App所依赖的Framework
方式,来实现对于Bug
的HotFix
,但是这个方案的缺点也是显而易见的它不符合Apple3.2.2
的审核规则,使用了这种方式是上不了Apple Store
的,它只能适用于公司内部的一些项目使用,同时这种方案其实并不适用于BugFix
,更适合App线上的大更新,因为其实我们项目中的引入的那些第三方的Framework
都是静态库。我们可以通过file
这个命令来查看我们的framework
到底是属于static
还是dynamic
。
方案二:React Native
React Native
支持用JavaScript
进行开发,所以可以通过更改JS文件实现App的HotFix
,但是这种方案的明显的缺点在于它只适合用于使用了React Native
这种方案的应用。
方案三:JSPatch
已经有一些方案可以实现动态打补丁,JSPatch
的优势是:
- JS语言:目前前端开发和终端开发有融合的趋势,作为扩展的脚本语言,JS在应用开发领域有更广泛的应用。
-
符合Apple规则:
JSPatch
更符合Apple的规则。iOS Developer Program License Agreement
里3.3.2提到不可动态下发可执行代码,但通过苹果JavaScriptCore.framework
或WebKit
执行的代码除外,JS正是通过JavaScriptCore.framework
执行的。 -
小巧:使用系统内置的
JavaScriptCore.framework
,无需内嵌脚本引擎,体积小巧。 -
风险:
JSPatch
让脚本语言获得调用所有原生OC方法的能力,不像web
前端把能力局限在浏览器,使用上会有一些安全风险:若在网络传输过程中下发明文JS,可能会被中间人篡改JS脚本,执行任意方法,盗取APP里的相关信息。当然,这可以对传输过程进行加密,或用直接使用https
解决。
6、致命缺陷
理论上还可以完全用JSPatch
实现一个业务模块,甚至整个APP,但不推荐这么做。
-
JSPatch
是通过Objective-C Runtime
的接口通过字符串反射找到对应的类和方法进行调用,这中间的字符串处理会损耗一定的性能,另外两种语言间的类型转换也有性能损耗,若用来做一个完整的业务模块,大量的频繁来回互调,可能有性能问题。 - 开发过程中需要用OC的思维写JS,丧失了脚本语言自己的特性。
-
JSPatch
没有IDE
支持,开发效率低。
若想动态为APP
添加模块,目前React Native
\ Flutter
等给出了很好的方案,解决了上述三个问题。
- JS/OC不会频繁通信,会在事件触发时批量传递,提高效率。
- 开发过程无需考虑OC的感受,遵从React框架的思想进行纯JS开发就行,剩下的事情
React Native
帮你处理好了。 -
React Native
连IDE都准备好了。
但React Native
并不会提供原生OC接口的反射调用和方法替换,无法做到修改原生代码,JSPatch
以小巧的引擎补足这个缺口,配合React Native
用统一的JS语言让一个原生APP时刻处于可扩展可修改的状态。
虽说2017年被苹果封杀了,但是大家还是偷偷摸摸混淆一下、改改类名继续在使用。毕竟bug还是不可避免的,有了JSPatch
万一出了问题我们还是能够抢救一下的。
三、JSPatch的核心原理
1、预加载部分
关于核心原理的讲解,网上有不少,但是几乎都是差不多,很多都还是引用了作者bang自己写的文档的内容,所以我采用一个例子方式进行讲解JSPatch
的主要运行流程,其实当然也会引用一些作者的简述,大家可以参照我写的流程讲述,在配合源码或者官方文档的介绍,应该就可以了解JSPatch
。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 环境的初始化
[JPEngine startEngine];
// js脚本的调用
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
......
}
首先是运行[JPEngine startEngine]
启动JSPatch
,启动过程分为以下两部分。首先通过JSContext
,声明了一些JS
方法到内存,这样我们之后就可以在JS
中调用这些方法,主要常用到的包括以下几个方法,同时会监听一个内存告警的通知。
+ (void)startEngine
{
JSContext *context = [[JSContext alloc] init];
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
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]);
};
context[@"_OC_getCustomProps"] = ^id(JSValue *obj) {
id realObj = formatJSToOC(obj);
return objc_getAssociatedObject(realObj, kPropAssociatedObjectKey);
};
context[@"_OC_setCustomProps"] = ^(JSValue *obj, JSValue *val) {
id realObj = formatJSToOC(obj);
objc_setAssociatedObject(realObj, kPropAssociatedObjectKey, val, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
};
.....
}
其次加载JSPatch.js
文件。JSPatch
文件的主要内容在于定义一些我们之后会用在的JS
函数,数据结构以及变量等信息,之后我会在用到的时候详细介绍。
2、脚本运行
我们定义如下的脚本。
require('UIAlertView')
defineClass('AppDelegate',['name', 'age', 'temperatureDatas'],
{
testFuncationOne: function(index) {
self.setName('xiejiapei')
self.setAge(24)
self.setTemperatureDatas(new Array(37.10, 36.78, 36.56))
var alertView = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles(
"title", self.name(), self, "OK", null)
alertView.show()
}
},
{
testFuncationTwo: function(datas) {
var alertView = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles(
"title", "DiDiChuXing", self, "OK", null)
alertView.show()
}
});
然后就是执行我们上面说的[JPEngine evaluateScript:script]
了,程序开始执行我们的脚本,但是在这之前,JSpatch
会对我们的脚本做一些处理,这一步同样也包括两个方面。首先需要给我们的程序加上try catch
的部分代码,主要目的是当我们的JS
脚本有错误的时候,可以catch
到错误信息。其次将所有的函数都改成通过__c
原函数的形式进行调用,也就是最后我们调用的脚本已经变成如下的形式了。
(function(){try{require('UIAlertView')
defineClass('AppDelegate',['name', 'age', 'temperatureDatas'],
{
testFuncationOne: function(index) {
self.__c("setName")('xiejiapei')
self.__c("setAge")(24)
self.__c("setTemperatureDatas")(new Array(37.10, 36.78, 36.56))
var alertView = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")(
"title", self.__c("name")(), self, "OK", null)
alertView.__c("show")()
}
},
{
testFuncationTwo: function(datas) {
var alertView = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")(
"title", "didichuxing", self, "OK", null)
alertView.__c("show")()
}
});
那么为什么需要用函数__c
来替换我们的函数呢,因为JS
语法的限制。对于没有定义的函数JS
是无法调用的,也就是调用UIAlertView.alloc()
其实是非法的,因为它采用的并不是消息转发的形式,所以作者原来是想把一个类的所有函数都定义在JS
上,也就是如下形式。
{
__clsName: "UIAlertView",
alloc: function() {…},
beginAnimations_context: function() {…},
setAnimationsEnabled: function(){…},
...
}
但是这种形式就必须要遍历当前类的所有方法,还要循环找父类的方法直到顶层,这种方法直接导致的问题就是内存暴涨,所以是不可行的,所以最后作者采用了消息转发的思想,定义了一个_c
的原函数,所有的函数都通过_c
来转发,这样就解决了我们的问题。
值得一提的是,我们的__c
函数就是在我们执行JSPatch.js
的时候声明到js
里的Object
方法里面去的,也就是下面这个函数。_customMethods
里面声明了很多需要追加到Object
上的函数。
;(function() {
......
// 给 JS 对象基类 Object 的 prototype 加上 __c 成员
for (var method in _customMethods) {
if (_customMethods.hasOwnProperty(method)) {
Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false})
}
}
......
}
3、require
调用 require('UIAlertView')
后,就可以直接使用UIAlertView
这个变量去调用相应的类方法了,require
做的事很简单,就是在JS
全局作用域上创建一个同名变量,变量指向一个对象,对象属性 __clsName
保存类名,同时表明这个对象是一个 OC Class
。
var _require = function(clsName) {
if (!global[clsName]) {
// 在JS全局作用域上创建一个同名变量
global[clsName] = {
// 变量指向一个对象,对象属性__isCls表明这是一个OC Class
__isCls: 1,
// __clsName保存类名,在调用方法时会用到这两个属性
__clsName: clsName
}
}
return global[clsName]
}
这样我们在接下来调用UIAlertView.__c()
方法的时候系统就不会报错了,因为它已经是JS
中一个全局的Object
对象了。
{
__clsName: "UIAlertView"
}
4、defineClass
接下来我们就要执行defineClass
函数了。defineClass
函数可接受四个参数。第一个参数是字符串,表示需要替换或者新增的类名:继承的父类名 <实现的协议1,实现的协议2>。
// [属性]、{实例方法}、{类方法}
global.defineClass = function(declaration, properties, instMethods, clsMethods)
当我们调用这个函数以后主要是做三件事情。首先执行_formatDefineMethods
方法,主要目的是修改传入的function
函数的的格式,以及在原来实现上追加了从OC回调回来的参数解析。然后执行_OC_defineClass
方法,也就是调用OC的方法,解析传入类的属性、实例方法、类方法,里面会调用overrideMethod
方法,进行method swizzing
操作,也就是方法的重定向。最后执行_setupJSMethod
方法,在js
中通过_ocCls
记录类实例方法、类方法。
a、_formatDefineMethods
可以发现,具体实现是遍历方法列表对象的属性(方法名),然后往js
空对象中添加相同的属性,它的值对应的是一个数组,数组的第一个值是方法名对应实现函数的参数个数,第二个值是一个函数(也就是方法的具体实现)。
// 参数一:一个方法列表js对象
// 参数二:一个新的js空对象
var _formatDefineMethods = function(methods, newMethods, realClsName) {
for (var methodName in methods) {
if (!(methods[methodName] instanceof Function)) return;
(function(){
var originMethod = methods[methodName]
newMethods[methodName] = [originMethod.length, function() {
try {
// 通过OC回调回来执行,获取参数
var args = _formatOCToJS(Array.prototype.slice.call(arguments))
var lastSelf = global.self
global.self = args[0]
if (global.self) global.self.__realClsName = realClsName
// 删除前两个参数:在OC中进行消息转发的时候,前两个参数是self和selector
// 我们在实际调用js的具体实现的时候,需要把这两个参数删除
args.splice(0,1)
var ret = originMethod.apply(originMethod, args)
global.self = lastSelf
return ret
} catch(e) {
_OC_catch(e.message, e.stack)
}
}]
})()
}
}
_formatDefineMethods
作用,简单的说,它把defineClass
中传递过来的js
对象进行了修改。
原来的形式是:
{
testFuncationOne:function(){...}
}
修改之后是:
{
testFuncationOne: [argCount, function (){...新的实现}]
}
传递参数个数的目的是,runtime
在修复类的时候,无法直接解析原始的js
实现函数,那么就不知道参数的个数,特别是在创建新的方法的时候,需要根据参数个数生成方法签名,也就是还原方法名字,所以只能在js
端拿到js
函数的参数个数,传递到OC端。
// js 方法
initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles
// oc 方法
initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:
b、_OC_defineClass
❶ 使用NSScanner分离classDeclaration,分离成三部分
- 类名:className
- 父类名:superClassName
- 实现的协议名:protocalNames
❷ 使用NSClassFromString(className)获得该Class对象
若该Class
对象为nil
,则说明JS
端要添加一个新的类,使用objc_allocateClassPair
与objc_registerClassPair
注册一个新的类。若该Class
对象不为nil
,则说明JS
端要替换一个原本已存在的类。
❸ 根据从JS端传递来的实例方法与类方法参数,为这个类对象添加/替换实例方法与类方法
添加实例方法时,直接使用上一步得到class
对象。添加类方法时需要调用objc_getMetaClass
方法获得元类。如果要替换的类已经定义了该方法,则直接对该方法替换和实现消息转发。
否则根据以下两种情况进行判断。遍历protocalNames
,通过objc_getProtocol
方法获得协议对象,再使用protocol_copyMethodDescriptionList
来获得协议中方法的type
和name
。匹配JS
中传入的selectorName
,获得typeDescription
字符串,对该协议方法的实现消息转发。
若不是上述两种情况,则js
端请求添加一个新的方法。构造一个typeDescription
为@@:\@*
的IMP
,将这个IMP
添加到类中。返回类型为id
,参数值根据JS
定义的参数个数来决定。新增方法的返回类型和参数类型只能为id
类型,因为在JS
端只能定义对象。
❹ 为该类添加setProp:forKey和getProp:方法
使用objc_getAssociatedObject
与objc_setAssociatedObject
让JS
脚本拥有设置property
的能力。
❺ 返回{className:cls}JS脚本
不过其中还包括一个overrideMethod
方法,不管是替换方法还是新增方法,都是使用overrideMethod
方法。它的目的主要在于进行method swizzing
操作,也就是方法的重定向。
我们把所有的消息全部都转发到ForwardInvocation
函数里去执行(不知道的同学请自行补消息转发机制),这样做的目的在于,我们可以在NSInvocation
中获取到所有的参数,这样就可以实现一个通用的IMP
,任意方法任意参数都可以通过这个IMP
中转,拿到方法的所有参数回调JS
的实现。于是overrideMethod
其实就是做了如下这件事情。
具体实现,以替换 UIViewController
的 -viewWillAppear:
方法为例。把UIViewController
的-viewWillAppear:
方法通过class_replaceMethod()
接口指向_objc_msgForward
这个全局 IMP
,OC 调用方法不存在时都会转发到这个IMP
上,这里直接把方法替换成这个IMP
,这样调用这个方法时就会走到-forwardInvocation:
。
为UIViewController
添加-ORIGviewWillAppear:
和-_JPviewWillAppear:
两个方法,前者指向原来的IMP
实现,后者是新的实现,稍后会在这个实现里回调JS
函数。
改写UIViewController
的-forwardInvocation:
方法为自定义实现。一旦OC里调用 UIViewController
的-viewWillAppear:
方法,经过上面的处理会把这个调用转发到-forwardInvocation:
,这时已经组装好了一个NSInvocation
,包含了这个调用的参数。在这里把参数从 NSInvocation
反解出来,带着参数调用上述新增加的方法-JPviewWillAppear:
,在这个新方法里取到参数传给JS
,调用JS
的实现函数。
c、_setupJSMethod
最后的一步是把之前所有的方法以及属性放入 _ocCls
中保存起来,最后再调用require
把类保存到全局变量中。到这一步为止,我们的JS
脚本中的所有对象已经通过runtime
替换到我们的程序中去了,也就是说,剩下的就是如何在我们触发函数以后,能正确的去执行JS
中函数的内容。
var _setupJSMethod = function(className, methods, isInst, realClsName) {
for (var name in methods) {
var key = isInst ? 'instMethods': 'clsMethods',
func = methods[name]
_ocCls[className][key][name] = _wrapLocalMethod(name, func, realClsName)
}
}
Demo
Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo