初窥JSPatch

Hotfix是解决线上bug的重要手段之一,iOS开发中,如今流行的成熟解决方法之一就是JSPatch,本文主要是对JSPatch应用进行一定的介绍,并未做原理分析.

谈起hotfix,是个不小的话题,而JSPatch以他灵活,使用方便而且功能强大的特点,使它成为解决线上重大bug的”黑武器”,强大,危险.所以在开头要说明,不用使用JSPatch来修改一些小的错误,只有发生重大bug的时候才使用,原子弹固然威力大,但造成的后果也是影响深远的.一些界面可以采用其他方案替代,提高app的动态性,比如使用web view等方式,这里不做深究.下面开始介绍JSPatch.

JSPatch是微信读书团队的大神bang开源项目,所以你是可以看到这个项目的核心源码,你可以通过CocoaPods集成到项目中,也可以手动集成,也可以通过SDK的方式集成,这里是JSPatch Platform的集成说明文档,简单来讲,如果你要自己搭建后台,进行修复包的部署等工作,直接使用开源代码即可,这种比较适用于大公司或者业务敏感,个性化定制功能的公司,而对公司来说,作为一般性需求或者只是在个人项目中使用的话,完全可以借助平台,而且平台也提供了付费功能.对于日请求量小于1w的用户是免费的.需要进行灰度测试,在线监控等功能,需要进行付费.

集成

1
pod '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
[JSPatch startWithAppKey:@"Your AppKey"];
// 可以多次调用用来同步服务器状态
[JSPatch sync];
// 程序启动的时候开启JSEngine
[JPEngine startEngine];
// 执行js代码
// 官方示例
// 直接调用js
[JPEngine evaluateScript:@"\
var alertView = require('UIAlertView').alloc().init();\
alertView.setTitle('Alert');\
alertView.setMessage('AlertView from js'); \
alertView.addButtonWithTitle('OK');\
alertView.show(); \
"];
// 执行网络js代码
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/test.js"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[JPEngine evaluateScript:script];
}];
// 执行本地js代码
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];

API示例

调用OC类之前需要调用require(class name)

1
2
requare(`UIView`)
var view = UIView.alloc().init()

也可使用的时候再进行声明

1
require(`UIColor`).redColor()

参数传递
和OC用法一样,直接进行传递即可

1
2
3
var view = UIView.alloc().init();
var superView = UIView.alloc().init()
superView.addSubview(view)

defineClass
defineClass(classDeclaration, [properties,] instanceMethods, classMethods)
@param classDeclaration: 字符串,类名/父类名和Protocol
@param properties: 新增property,字符串数组,可省略
@param instanceMethods: 要添加或覆盖的实例方法
@param classMethods: 要添加或覆盖的类方法

1
2
3
4
5
6
7
8
9
10
11
// OC
@implementation JPTestObject
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
@end
// JS
defineClass("JPTableViewController", { tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
...
},
})

把JSPatch的Log添加到自己的日志里面

1
2
3
4
[JSPatch setLogger:^(NSString *msg) {
//msg 是 JSPatch log 字符串,用你自定义的logger打出
YOUR_APP_LOG(@"%@", msg);
}];

官方修复线上bug示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@implementation XRTableViewController
⁃ (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *content = self.dataSource[[indexPath row]]; //可能会超出数组范围导致crash
XRViewController *controller = [[JPViewController alloc] initWithContent:content];
[self.navigationController pushViewController:controller];
}
@end
//main.js
defineClass("XRTableViewController", {
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var row = indexPath.row()
if (self.dataSource().length > row) { //加上判断越界的逻辑
var content = self.dataArr()[row];
var controller = XRViewController.alloc().initWithContent(content);
self.navigationController().pushViewController(controller);
}
}
})

脚本删除
如果是通过JSPatch Platform,在操作界面进行删除脚本即可;如果是自搭建平台,需要调用JPCleaner的clean方法,进行指定类的或者全部代码进行清除.
更多基本用法查阅官方文档:JSPatch基础用法

传输安全问题

由于JSPatch是通过运行时进行消息分发,因此功能强大, 因此一旦被黑客攻击,下发恶意补丁包,那app基本就会被彻底攻破,因此在使用JSPatch SDK的时候,对js文件进行了RSA加密
主要流程就是对服务端进行js文件的MD5加密,然后使用RSA私钥对MD5进行一次加密,一起下发给客户端;
客户端拿到数据之后,RSA公钥进行解密,拿到MD5值,然后自己进行计算下载的js文件的MD5,进行校验,判断是否被修改,进行分情况处理.

参考

JSPatch官方文档说明;
JSPatch github源代码