Dec 2

JSPatch随意初探

Lrdcq , 2016/12/02 20:04 , 程序 , 閱讀(3881) , Via 本站原創
JSPatch是iOS开发中最常见的热修复/热更新方案。简单的说,JSPatch主要基于jscore运行时动态允许脚本和ocruntime的动态特性,通过js脚本控制oc上下文中的程序以达到目的。选择jscore作为脚本引擎很大目的是为了绕过appstore关于程序动态特性的上架审核规则(reactnative同理),而基于oc的runtime特性是能实现热修复的基础,因此在swift中,不是基于nsobject的类是无法使用这个热更新的,这也是现在swift还没在中大型工程中广泛使用的主要原因之一。

使用

根据官方文档,我们很快就能搭建起一个基本的demo并且运行:

1.在pod中引入JSPatch
2.在AppDelegate加载入一个js文件并运行,从网络加载应该是最方便的:
[JPEngine startEngine];

// exec js file from network
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1/test.js"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [JPEngine evaluateScript:script];
}];

3.实际编写一个js脚本。由于最常用的场景是修复bug或者错误,我们往往需要做的是重写一个类的某个方法。在我当前的项目中的一个cell有一个label通过setItem方法给予了文本,我希望修改给予文本的方式,于是写了这样一个脚本:
require("NSString")
defineClass("KLMCartViewCell", {
  setItem: function(item) {
    self.ORIGsetItem(item);

    var _weight = self.valueForKey("_weight");
    _weight.setText(NSString.stringWithFormat("hahahahah %@",item.skuUnit()));

  }
});

将这段脚本挂载到本地服务器上对应的位置"http://127.0.0.1/test.js",在xcode模拟器中运行app,就可以看到想到的效果了。

脚本细节

就这么几行js脚本,也有一些需要注意的地方。

1. 这里是重写的setItem方法,如果是多参数方法怎么办?用下划线连接方法名,如tableView_didSelectRowAtIndexPath,当然调用方法的时候也是这个理。

2. 虽然说是打补丁替换方法,实际上做的是方法交换,因此我们肯定可以通过一定方法调用到被交换的函数,即self.ORIGsetItem()即可。

3. oc对象的属性(property)没办法直接调用,比如上面我调用item.skuUnit是取不到的,稳定的方法是通过getter和setter,即item.skuUnit()和item.setSkuUnit()就可以了。

4. 但内部的成员变量没有getter和setter怎么办,比如上面的_weight,事实是通过self.valueForKey("_weight")这样的方法取到的。

5. 字符串相加,本来想直接使用"hahahahah "+item.skuUnit(),发现无法得到预期的结果,后判断js中的字符串和nsstring无法直接相加,因此还是老老实实改用NSString.stringWithFormat来串接字符串。

总之,脑子里可以回想一下JSPatch的实现原理(作者会将所有的方法调用转换为__c("xxx",xxx)来使用),那么就不会犯错误,能调用方法尽量调用方法,用oc的思路来写补丁js,不要想着这是js即可。

架构

看到上面的helloworld性质的demo,我们很快就能意识到这段代码并不能直接在项目中使用。显然,一段通过网络加载的js,加载时间是不稳定的,是否能加载成功也不确定,是否是正确无误的更不能确定,因此我们需要从多个角度来保障js脚本的执行。

加载器

虽然JSPatch自带了一个简陋的loader给我们使用,但是显然不能满足我们补丁版本管理的需求。因此我们自行设计了一个加载器,流程如下图:
點擊在新視窗中瀏覽此圖片
可以看到主流程分为两部分:

1. 检查本地是否有可用(版本没过期)的js补丁,如果有,毫不迟疑立刻运行。
2. 访问接口检查是否有js更新,如果有,下载最新的js,下载后跟新本地补丁信息,但不会立刻使用。

显然,如果是启动即死的bug或者崩溃肯定不能使用jspatch,因此这样的加载管理即可以保障js补丁的稳定运行,也可以保障及时更新。另外加上了连续崩溃删除js作为兜底的策略,保障app可重置性。因此,这应该是比较靠谱的方案。
当然对应的,后端服务需要提供一个包含js文件版本,app版本的管理平台和数据查询接口。

js加密

作为可以修改app所有代码的热修复补丁,一旦被人劫持或者修改了,肯定会出现严重的安全问题,因此js传输过程中需要加密和校验。

1. 还好,自从有了https,传输过程中的加密基本不用愁了。
2. 至于校验,一般来说在查询接口中就会提供该js文件的校验码,一般文件md5加盐就可以了,这个场景下没必要双向加密。文件下载完成之后对文件进行本地校验,保障文件没有被丢失或者篡改。

当然,上面这一套加载和传输方案无论在前段和后端都有很多可改进和优化的地方,比如版本预下载之类的。这些调研技术也可以在今后使用到react-native的动态加载和使用上。

其他

阅读原理详解不但可以详细了解其运行原理,更可以避免踩不必要的坑,因此,使用前建议务必阅读:https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3,同时当然也需要阅读代码,毕竟活生生的黑科技的正确使用方式。
然后,作为黑科技这种骚东西,记住一个字,稳,就可以了。
关键词:ios , oc , jspatch
logo