JSPatch技术文档

一、背景需求介绍

为什么我们需要一个热修复(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 = this
  return 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]];  //可能会超出数组范围导致crash
  JPViewController *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
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

时间: 2024-10-14 15:24:11

JSPatch技术文档的相关文章

开源进销存PSI - 技术文档目录

开源进销存PSI技术文档目录 1.本地环境搭建 2.总体技术架构 PSI概要设计文档在这里

d3js技术文档

D3js技术文档 概述 D3 allows you to bind arbitrary data to a Document Object Model (DOM), and then apply data-driven transformations to the document. For example, you can use D3 to generate an HTML table from an array of numbers. Or, use the same data to cr

OCR识别技术文档识别怎么用

OCR识别技术文档识别的概括 我们常说的OCR.文字识别.OCR识别技术文档识别是指通过电子设备等将纸质上的文字识别出来,形成可编辑的文字. OCR识别技术文档识别的流程 随着扫描仪的普及与广泛应用,再加上摄像头迅速发展的手机等智能终端设备的应用,OCR识别技术文档识别软件越来越被应用于各种业务系统中. 常规的OCR文字识别处理的过程包括: 1.图像输入.预处理:二值化图片.噪声去除.倾斜较正: 2.版面分析:把页面分为横排文本.竖排文本.表格.图片等不同区域,帮助字符切割.识别OCR: 3.设

Atitit usrQBK1600 技术文档的规范标准化解决方案

1.1. Keyword关键词..展关键词,横向拓展比较,纵向抽象细化拓展知识点1 1.2. 标题必须有高大上词汇,参考文章排行榜,1 1.3. 标题带语言关键词c#.net js javascript  c++ python1 1.4. 标题最好有英文版本的,方便英文查询1 1.5. 标题atitit前缀,attilax总结结尾方便查询1 1.6. 标题后面带上版本号v2 qbf等,方便时间对比1 1.7. 正文要求 修辞好(引用典故名句,成语 等,使用修辞方法)2 1.8. 引用寓言故事 类

Xamarin技术文档------VS多平台开发

此技术业余时间研究,仅供大家学习参考,不涉及深入研究,有一定开发基础的人员,应该都能较快上手. 一.简介 Xamarin始创于2011年,旨在使移动开发变得难以置信地迅捷和简单.Xamarin的产品简化了针对多种平台的应用开发,包括iOS.Android.Windows Phone和Mac App.Xamarin由许多著名的开源社区开发者创立和参与,而且也是Mono项目的主导者--C#与.NET框架的开源.跨平台实现. 作为一个跨平台开发框架,Xamarin.Mobile有很多优点.在这一框架内

如何快速阅读并理解英文的技术文档

作为一名程序员,要实现我们的产品,首先需要选择一种或几种编程语言,其次是使用各种工具和第三方库. 而在这个过程中,就少不了对这些语言.工具和第三方库的下载和学习. 下载一般都非常简单,但是关于如何使用,相信大家都会有各种各样的学习方法. 但是不管通过什么方式,追根溯源都会来到官方文档. 那么问题就来了!目前来说,大部分的官方文档都是英文的,如何才能快速的理解并使用官方文档呢? 今天,把自己的学习方法拿出来,和大家一起分享一下,希望大家可以尽量少走一些弯路,尽快的找到bug的解决方法. 说起来很简

DL动态载入框架技术文档

DL动态载入框架技术文档 DL技术交流群:215680213 1. Android apk动态载入机制的研究 2. Android apk动态载入机制的研究(二):资源载入和activity生命周期管理 3. APK动态载入框架DL解析 4. Android 使用动态载入框架DL进行插件化开发 5. DL插件开发笔记 6. DL开发注意事项 附:DL层次结构图

Boost.Asio技术文档

Christopher Kohlhoff Copyright ? 2003-2012 Christopher M. Kohlhoff 以Boost1.0的软件授权进行发布(见附带的LICENSE_1_0.txt文件或从http://www.boost.org/LICENSE_1_0.txt) Boost.Asio是用于网络和低层IO编程的跨平台C++库,为开发者提供了C++环境下稳定的异步模型. 综述 基本原理 应用程序与外界交互的方式有很多,可通过文件,网络,串口或控制台.例如在网络通信中,完

斯巴鲁汽车 技术文档下载方法

昨天深夜,突然朋友找我帮忙,下载斯巴鲁的技术文档.原本以为是因为某些原因他访问不到国外的网站,结果却让我惊呆了!妈蛋,这pdf有1000多个啊··· 朋友在国外的论坛上找到有人可以下载,而且已经贴上了源码,只是他不懂. 论坛地址为:http://www.subaruoutback.org/forums/138-gen-5-2015-present/280682-2016-owner-s-service-manuals-posted.html 这是文档下载的网站:http://techinfo.s