【iOS】hotfix之JSPatch

JSPatch可实现项目上线后, 热修复已知的bug. JSPatch 可以让你用 JavaScript 书写原生 iOS APP。只需在项目引入极小的引擎,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug。
JSPatch源码https://github.com/bang590/JSPatch, 以及免自建服务器集成框架http://jspatch.com/


JavaScript对应OC语法说明

https://github.com/bang590/JSPatch/blob/master/README-CN.md(中文参考)
http://bang590.github.io/JSPatchConvertor/(OC转换为JS在线工具)
https://github.com/bang590/JSPatchX(Xcode写JSPatch自动补全插件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 调用require引入要使用的OC类
require('UIView, UIColor, UISlider, NSIndexPath')
// 调用类方法
var redColor = UIColor.redColor();
// 调用实例方法
var view = UIView.alloc().init();
view.setNeedsLayout();
// 设置属性
view.setBackgroundColor(redColor);
// 获取属性
var bgColor = view.backgroundColor();
// 多参数方法名用'_'隔开:
// OC:NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
var indexPath = NSIndexPath.indexPathForRow_inSection(0, 1);
// 如果要把 `NSArray` / `NSString` / `NSDictionary` 转为对应的 JS 类型,使用 `.toJS()` 接口.
var arr = require('NSMutableArray').alloc().init()
arr.addObject("JS")
jsArr = arr.toJS()
console.log(jsArr.push("Patch").join('')) //output: JSPatch
// 在JS用字典的方式表示 CGRect / CGSize / CGPoint / NSRange
var view = UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100});
var x = view.bounds().x;
// block 从 JavaScript 传入 Objective-C 时,需要写上每个参数的类型。
// OC Method: + (void)request:(void(^)(NSString *content, BOOL success))callback
require('JPObject').request(block("NSString *, BOOL", function(ctn, succ) {
if (succ) log(ctn)
}));
// GCD
dispatch_after(function(1.0, function(){
// do something
}))
dispatch_async_main(function(){
// do something
})

JSPatch热修复使用

一般步骤

1.# import “JPEngine.h”
2.调用[JPEngine startEngine]
3.通过[JPEngine evaluateScript:@””]接口执行 JavaScript。(可以直接写JavaScript, 也可用从文件读入, 远程获取)

实例代码

代码功能为: 改变ViewController中预先定义的row行数, Cell背景颜色, 与修复越界 .

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[JPEngine startEngine];
//读取本地的test.js文件
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"js"];
NSLog(@"%@",sourcePath);
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
[self.window makeKeyAndVisible];
return YES;
}

test.js文件中内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
defineClass("ViewController", {
tableView_numberOfRowsInSection: function(tableView, section) {
return 4;
},
tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("cell")
if (!cell) {
cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(0, "cell")
}
cell.textLabel().setText("xn");
var redColor = require('UIColor').redColor();
cell.setBackgroundColor(redColor);
return cell
},
//instance method definitions
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var row = indexPath.row()
if (self.dataSource().length > row) { // 修复数组越界
var content = self.dataArr()[row];
}
}
}, {});

这些JS代码不必手写, 可以先写出OC代码, 然后用在线工具转换即可.

总结

JSPatch能做到通过JS调用和改写OC方法最根本的原因是 Objective-C 是动态语言,OC上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名和方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现,还可以新注册一个类,为类添加方法。所以 JSPatch 的原理就是:JS传递字符串给OC,OC通过 Runtime 接口调用和替换OC方法。JS的作用只是一个信使,具体实现还是靠OC。

以下两篇文章为作者自己写的JSPatch实现原理:

http://blog.cnbang.net/tech/2808/(实现原理一)
http://blog.cnbang.net/tech/2855/(实现原理二)

转载请注明出处,有疑问欢迎留言!