一、背景需求介绍

为什么我们需要一个热修复(hot-fix)技术?

  • 工作中容易犯错、bug难以避免。
  • 开发和测试人力有限。
  • 苹果Appstore审核周期太长,一旦出现严重bug难以快速上线新版本。
  • 作为生产力工具,用户有对稳定性和可靠性的需求。

二、JSPatch简介

JSPatch诞生于2015年5月,最初是腾讯广研高级iOS开发@bang的个人项目。
它能够使用JavaScript调用Objective-C的原生接口,从而动态植入代码来替换旧代码,以实现修复线上bug。
JSPatch在Github.com上开源后获得了3000多个star和500多fork,广受关注,目前已被应用在大量腾讯/阿里/百度的App中。

三、JSPatch与wax对比

JSPatch与Wax对比

最关键的是JSPatch可实现方法粒度的线上代码替换,能修复一切代码引起的Bug。
而Wax无法实现。

四、JSPatch实现原理

基础原理

Objective-C是动态语言,具有运行时特性,该特性可通过类名称和方法名的字符串获取该类和该方法,并实例化和调用。

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);

Javascript调用

我们可以用Javascript对象定义一个Objective-C类:

{__isCls: 1,__clsName: "UIView"
}

在OC执行JS脚本前,通过正则把所有方法调用都改成调用 __c() 函数,再执行这个JS脚本,做到了类似OC/Lua/Ruby等的消息转发机制:

UIView.alloc().init()
->
UIView.__c('alloc')().__c('init')()

给JS对象基类 Object 的 prototype 加上 c 成员,这样所有对象都可以调用到 c,根据当前对象类型判断进行不同操作:

Object.prototype.__c = function(methodName) {if (!this.__obj && !this.__clsName) return this[methodName].bind(this);var self = thisreturn function(){var args = Array.prototype.slice.call(arguments)return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)}
}

互传消息

JS和OC是通过JavaScriptCore互传消息的。OC端在启动JSPatch引擎时会创建一个 JSContext 实例,JSContext 是JS代码的执行环境,可以给 JSContext 添加方法。JS通过调用 JSContext 定义的方法把数据传给OC,OC通过返回值传会给JS。调用这种方法,它的参数/返回值 JavaScriptCore 都会自动转换,OC里的 NSArray, NSDictionary, NSString, NSNumber, NSBlock 会分别转为JS端的数组/对象/字符串/数字/函数类型。
对于一个自定义id对象,JavaScriptCore 会把这个自定义对象的指针传给JS,这个对象在JS无法使用,但在回传给OC时OC可以找到这个对象。对于这个对象生命周期的管理,如果JS有变量引用时,这个OC对象引用计数就加1 ,JS变量的引用释放了就减1,如果OC上没别的持有者,这个OC对象的生命周期就跟着JS走了,会在JS进行垃圾回收时释放。

方法替换

  1. 把UIViewController的 -viewWillAppear: 方法通过 class_replaceMethod() 接口指向 _objc_msgForward,这是一个全局 IMP,OC 调用方法不存在时都会转发到这个 IMP 上,这里直接把方法替换成这个 IMP,这样调用这个方法时就会走到 -forwardInvocation:

  2. 为UIViewController添加 -ORIGviewWillAppear:-_JPviewWillAppear: 两个方法,前者指向原来的IMP实现,后者是新的实现,稍后会在这个实现里回调JS函数。

  3. 改写UIViewController的 -forwardInvocation: 方法为自定义实现。一旦OC里调用 UIViewController 的 -viewWillAppear: 方法,经过上面的处理会把这个调用转发到 -forwardInvocation: ,这时已经组装好了一个 NSInvocation,包含了这个调用的参数。在这里把参数从 NSInvocation 反解出来,带着参数调用上述新增加的方法 -JPviewWillAppear: ,在这个新方法里取到参数传给JS,调用JS的实现函数。整个调用过程就结束了,整个过程图示如下:

JSPatch方法替换

最后一个问题,我们把 UIViewController 的 -forwardInvocation: 方法的实现给替换掉了,如果程序里真有用到这个方法对消息进行转发,原来的逻辑怎么办?首先我们在替换 -forwardInvocation: 方法前会新建一个方法 -ORIGforwardInvocation:,保存原来的实现IMP,在新的 -forwardInvocation: 实现里做了个判断,如果转发的方法是我们想改写的,就走我们的逻辑,若不是,就调 -ORIGforwardInvocation: 走原来的流程。

五、JSPatch代码示例

JSPatch在OC上的调用十分简单

- (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];
}

一个Javascript代码修复Objective-C的bug的示例:

@implementation JPTableViewController- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{NSString *content = self.dataSource[[indexPath row]];  //可能会超出数组范围导致crashJPViewController *ctrl = [[JPViewController alloc] initWithContent:content];[self.navigationController pushViewController:ctrl];
}@end

上述代码中取数组元素处可能会超出数组范围导致crash。如果在项目里引用了JSPatch,就可以下发JS脚本修复这个bug:

defineClass("JPTableViewController", {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);}}
}, {})

六、股单App的Hot-fix解决方案

1.版本更新策略

  • 考虑到下一个提交的App版本已经修复了上一个版本的bug,所以不同的App版本对应的补丁版本肯定也不同。同一个App版本下,可以出现递增的补丁版本。
  • 补丁为全量更新,即新版本补丁包括旧版补丁的内容,更新后新版补丁覆盖旧版补丁。
  • 补丁分为可选补丁和必选补丁,必选补丁用于重大bug的修复,如果不更新必选补丁则App无法继续使用。如下图2中,补丁版本v1234对应各自版本的用户,补丁v3为必须更新,补丁v1,v2,v4为可选补丁,则v1,v2的用户必须更新到v4才可使用;而v3的用户可先使用,同时后台静默更新到v4.

    股单App补丁版本更新策略

2.安全策略

安全问题在于JS 脚本可能被中间人攻击替换代码。可采取以下三种方法,股单App目前采用的是第三种:

1.对称加密。如zip 的加密压缩、AES 等加密算法。优点是简单,缺点是安全性低,易破解。若客户端被反编译,密码字段泄露,则完成破解。
2.HTTPS。优点是安全性高,证书在服务端未泄露,就不会被破解。缺点是部署麻烦,如果服务器本来就支持 HTTPS,使用这种方案也是一种不错的选择。
3.RSA校验。安全性高,部署简单。

RSA校验

详细校验步骤如下:
1.服务端计算出脚本文件的 MD5 值,作为这个文件的数字签名。
2.服务端通过私钥加密第 1 步算出的 MD5 值,得到一个加密后的 MD5 值。
3.把脚本文件和加密后的 MD5 值一起下发给客户端。
4.客户端拿到加密后的 MD5 值,通过保存在客户端的公钥解密。
5.客户端计算脚本文件的 MD5 值。
6.对比第 4/5 步的两个 MD5 值(分别是客户端和服务端计算出来的 MD5 值),若相等则通过校验。

3.客户端策略

客户端具体策略如下图:
1.用户打开App时,同步进行本地补丁的加载。
2.用户打开App时,后台进程发起异步网络请求,获取服务器中当前App版本所对应的最新补丁版本和必须的补丁版本。
3.获取补丁版本的请求回来后,跟本地的补丁版本进行对比。
4.如果本地补丁版本小于必须版本,则提示用户,展示下载补丁界面,进行进程同步的补丁下载。下载完成后重新加载App和最新补丁,再进入App。
5.如果本地补丁版本不小于必须版本,但小于最新版本,则进入App,不影响用户操作。同时进行后台进程异步静默下载,下载后补丁保存在本地。下次App启动时再加载最新补丁。
6.如果版本为最新,则进入App。

股单App客户端hot-fix策略

七、参考资料和文献:

1.https://github.com/bang590/JSPatch
2.https://github.com/mmin18/WaxPatch
3.https://github.com/probablycorey/wax
4.https://github.com/alibaba/AndFix
5.http://blog.cnbang.net/tech/2879/
6.http://blog.cnbang.net/works/2767/
7.http://blog.cnbang.net/tech/2808/
8.http://blog.zhaiyifan.cn/2015/11/20/HotPatchCompare/

文/上官soyo(简书作者)
原文链接:http://www.jianshu.com/p/0cb81bf23d7a
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

JSPatch技术文档相关推荐

  1. [技术文档] 一劳永逸,用USB设备制作多系统引导

    2019独角兽企业重金招聘Python工程师标准>>> [技术文档] 一劳永逸,用USB设备制作多系统引导  [复制链接] binghe27 艺有所成 当前离线 主题 8  UID ...

  2. 不写技术文档是个什么梗

    写文档在工作中很常见了,正规的公司都有文档,除非是很简单的东西. 文档用来给新人或不熟悉的人的看,出需求也要文档.只凭笔在本子上划几下不能让人懂. 凡是稍微复杂的东西一定用文档梳理流程,有的还有流程图 ...

  3. 做一个像Keras在线技术文档(Sphinx + GitHub + Read the Docs)

    之前看到Keras的中文文档十分心动也想自己做一个,最终被我做成了.这里写一下制作流程. 制作效果感觉还是不错的 链接:https://opencv-learning.readthedocs.io/z ...

  4. 代码统计工具1.1版本技术文档

    代码统计工具1.1版本技术文档 说明:主要记录自己在做这个项目的过程中用到的方法和相关技术 1.首先面临的问题就是怎样选择一个目录,网上搜索了一下,下面是解决方案(用到目录对话框) (1)从默认的磁盘 ...

  5. virtualbox中文技术文档_随笔--西门子STEP7中如何寻找技术文档

    西门子STEP7软件支持的编程语言除了常用的LAD/FBD/STL,还有SCL/GRAPH等,应该说除了LAD/STL之外,SCL和GRAPH也是比较常用的,至少对我个人来说是这样,但是每种指令在不同 ...

  6. SLAM学习--视觉slam学习教材推荐(附相关技术文档下载链接)

    (理论上看完前三本,足够掌握视觉slam的所有理论知识,实践部分参考各种开源代码) 一.<视觉slam十四讲>,高翔,清华大学出版社,(目前已出第二版,优先推荐) 以上教材,其实是基于国外 ...

  7. 【资源推荐】良心之作!超过 10000+ 的互联网团队正在使用的在线 API 文档、技术文档工具...

    搞开发的同学都知道一个好的 API 文档是有多重要! 每当接手一个别人开发好的项目,看着那些没有注释的代码,真的头大. 程序员都很希望别人能写技术文档,因为可以提高自己开发的效率,而往往自己却很不希望 ...

  8. VuePress 手摸手教你搭建一个类Vue文档风格的技术文档/博客

    前言: VuePress是尤大为了支持 Vue 及其子项目的文档需求而写的一个项目,VuePress界面十分简洁,并且非常容易上手,一个小时就可以将项目架构搭好.现在已经有很多这种类型的文档,如果你有 ...

  9. 小明分享|sigmstar SSD201/SSD202 针对MIPI的LCD屏配置操作说明分享,更多Sigmastar相关技术文档及沟通讨论,可以给小明留言,小明邀请你进入我们的技术沟通群

    小明会定期分享SigmastarSSD201/202相关芯片技术文档!我们也有一个Sigmastar技术沟通群聊!有感兴趣的开发小伙伴也可以留言给小明,小明邀请你进入! SSD20X 平 台 LCD ...

最新文章

  1. mysql 5.7解压缩,Mysql5.7 windows解压缩ZIP版安装
  2. 八、进程调度的时机、切换与过程,调度方式
  3. JavaWeb基础—数据库连接池DBCP、C3P0
  4. 把项目从VS2005升级到VS2013
  5. map vs hash_map
  6. OVS 非VXLAN的收发包调用栈(二十二)
  7. python中的tail()_让VASP实现固定应力张量计算的python脚本
  8. 实验吧—Web——WP之 Forms
  9. HTTP 长连接原理
  10. java 正则表达式 去掉 文章头部和尾部的空格(全角,半角)、制表符、换页符
  11. 带壳截图 android,给手机截屏带个「套」:带壳截图应用合辑
  12. wordpress相册_如何在WordPress中使用相册创建相册
  13. java blob 保存文件_java将文件保存为二进制流到oracle表中的blob字段 | 学步园
  14. 性能测试--03企业级性能测试与分析
  15. uniapp开发小程序之上传图片(拍照或从相册中取)
  16. java8 —— Stream( 流 )
  17. 2022年终总结-两年Androider的成长之路
  18. WordPress批量添加、修改、删除自定义字段的sql命令
  19. Access转出到Mysql,咱自己动手丰衣足食.. -- 纯JS代码
  20. 人生第一次面试还是霸面----cvte

热门文章

  1. 天翼云探索云原生、边缘计算融合新思路
  2. Echarts 3D立体柱状图(源码+例图)
  3. 创建DC时序约束文件
  4. Syzkaller学习笔记---更新syz-manager(二)
  5. DOS命令下的move路径的问题
  6. mysql 经纬度坐标 距离 范围查询
  7. 百度,阿里,腾讯薪资体系
  8. 【已解决:teamview使用的设备数量受限,您已达到设备数量上限】
  9. crazy的比较级最高级_英语小问题:crazy比较级是crazier还是more crazy?
  10. Metasploit工具学习(二)