价值100W的经验分享: 基于JSPatch的iOS应用线上Bug的即时修复方案,附源码.

限于iOS AppStore的审核机制,一些新的功能的添加或者bug的修复,想作些节日专属的活动等,几乎都是不太可能的.从已有的经验来看,也是有了一些比较经常使用的解决方案.本文先是会简单说明对比大部分方案,而后会注重阐述基于JSPatch的在线更新机制的设计和实现.对于任何一家有必定用户基础的iOS应用来讲,在线更新技术所产生的直接和间接价值都将远远超过100W.理解,并掌握它;实在没有时间,就记住它,由于这篇文章不只仅是讨论.react

实例地址:https://github.com/ios122/ios122ios

几种在线更新方案的对比: 为何是JSPatch?

方案一: 申请"加急审核"

  • 方法: 提交应用时,选择"加急审核".git

  • 优势: 操做简单,只须要从新上传应用便可;github

  • 缺点:"加急审核",确定是不能常用的.web

  • 简评: 我想,这多是大多数公司遇到紧急问题时,最常使用的方案.一个应用,每一年是有若干次机会申请"加急审核",来缩短应用新版本的审核周期.一般审核周期是7天左右;"加急审核",一般只须要3天左右.编程

方案二: 使用 webview + Html5 页面

  • 方法: 特定的可能须要常常换的页面使用WebView来显示,内部使用Html5的内容来填充.当须要改变页面时,只须要改变下服务器接口返回的内容便可.react-native

  • 优势: 对于内容的更新,足够灵活和迅速.缓存

  • 缺点: 没法修复非HTML5页面的Bug;Html5 交互和UI一般逊色于原生页面.安全

  • 简评: 混合应用经常使用的方式,如PhoneGap等;对于大多数原生应用来讲,此方案基本无适用性.ruby

方案三: 编写基于ReactNative的应用

  • 方法: 使用 ReactNative 来编写应用或应用的部分页面,更多介绍参见: React Native 官方文档中文版

  • 优势: 原生UI,原生交互,支持服务器方式在线更新应用.

  • 缺点: 对于非ReactNative编写的页面无能为力.

  • 简评: 我的主观是很看好 ReactNative的,也在慢慢踩坑;但现实是大部分公司的已有项目是基于Objetive-C的,因此基于ReactNative的在线更新策略,目前对于大多说公司来讲也并不具备可行性.

方案四: 基于JSPatch实如今线补丁式更新

  • 方法: 在本身的项目中引入JSPatch库,而后参见下文继续讨论的方案细节实施便可.JSPatch的入门使用,参见: http://www.ios122.com/2015/11/jspatch/

  • 优势: 支持操做全部工程中引入的CocoaTouch库与各类第三方库.可彻底自由定义与重写已有代码的逻辑.

  • 缺点: JS语法操做API,语法转换有必定成本.

  • 简评: 大多数时候,咱们须要的只是重写下某个方法,甚至某个判断,某个默认值,就能够很好地修复某个线上的Bug.因此,JSPatch,已经够用了.固然,若是是对于复杂的新功能的添加的话,建议仍是提交审核吧.另外,不得不说一句,JSPatch + ReactNatvie 未来或许会成为一个很强力的组合,前者侧重于Bug的修复,后者侧重于复杂新需求的添加.本文接下来的篇幅将注重讨论基于JSPatch的线上Bug的即时修复方案.

关于使用JSPatch几个技术点的分析与实现.

基本实现原理

安装本地全部补丁 --> 联网更新补丁信息,并安装有更新或新增长的补丁.注意此处的安装,指的是执行如下JS文件中的代码.此段代码会替换某个类的默认实现.当App运行到须要某个类的某个被JSPatch替换的方法时,会走JS定义的逻辑,而再也不是源代码中默认的逻辑.能够看下DEMO.另外,咱们的应用和示例中都使用了Objection这个依赖注入的库,你可能也要先温习下: [Objection,一个轻量级的Objective-C依赖注入框架

](http://www.ios122.com/2015/11/objection/)

文件 md5 值的获取与校验

mac上,获取某个文件的md5值,直接在终端输入命令:

md5 文件完整路径.

关于校验md5的代码,其实最核心的是如何在oc中使用代码获取某个文件的md5值,而后进行比对.网上的示例不少,但可能不太靠谱,下面贴一段确实可行的,注意要引入系统库 #include <CommonCrypto/CommonDigest.h>:

/**
 *  获取文件的md5信息.
 *
 *  @param path 文件路径.
 *
 *  @return 文件的md5值.
 */
-(NSString *)mcMd5HashOfPath:(NSString *)path
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    // 确保文件存在.
    if( [fileManager fileExistsAtPath:path isDirectory:nil] )
    {
        NSData *data = [NSData dataWithContentsOfFile:path];
        unsigned char digest[CC_MD5_DIGEST_LENGTH];
        CC_MD5( data.bytes, (CC_LONG)data.length, digest );
        
        NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
        
        for( int i = 0; i < CC_MD5_DIGEST_LENGTH; i++ )
        {
            [output appendFormat:@"%02x", digest[i]];
        }
        
        return output;
    }
    else
    {
        return @"";
    }
}

补丁状态的管理

能够绝不夸张的说,正确理解并定义补丁状态,是整个在线更新机制最核心的一步,其余的真的只是辅助:

/**
 *  补丁状态.
 */
typedef enum : NSUInteger {
    YFPatchModelStatusUnKnownError, //!< 未知错误.
    YFPatchModelStatusUnInstall, //!< 还没有开始安装.应用初始时,全部本地补丁状态均为此;补丁更新或新增的补丁;在下载完成后,状态也会设置为此.
    YFPatchModelStatusSuccess, //!< 安装成功.
    YFPatchModelStatusFileNotExit, //!< 本地补丁文件不存在.
    YFPatchModelStatusFileNotMatch, //!< 本地补丁MD5与给定的MD5值不匹配.
    YFPatchModelStatusUpdate, //!< 此补丁有更新.即服务器最新返回的补丁列表中包含此补丁,但补丁的md5或url已改变.
    YFPatchModelStatusAdd //!< 此补丁为新增的.即服务器最新返回的补丁列表中新添加的补丁.
} YFPatchModelStatus;

补丁状态的具体管理策略,参见 https://github.com/ios122/ios122/blob/master/iOS122/iOS122/samples/JSPatchOnline/patch/YFPatchViewModel.m

如何在本地测试JS可用性

这个是必然要考虑的问题,一种方式是能够在工程中放一个demo.js供Debug模式下调试;另外一种方式是本地返回固定的假数据,可是假数据自己的 JS文件地址,md5,版本号等都是真实的.

/**
 *  测试模式下,会执行此方法,以验证某个JS文件的做用.默认使用本地demo.js.
 */
- (void)mcDebug
{
#ifdef DEBUG
    NSString * path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
    [self mcEvaluateScriptFile: path];
#endif

}

补丁的增删改查.

  • 增:服务器返回的补丁,本地不存在时,会默认下载存储,并执行.

  • 删: 服务器返回的补丁集中,不包含本地的某个补丁,则此补丁下次不会再被执行.

  • 改: 服务器返回的补丁,本地包含,但md5值变化,此时会从新下载此补丁.

  • 查: 会默认在应用启动时,执行全部存在,且md5值匹配的补丁.补丁集的信息,会在每次联网更新时更新.此处使用的是一个缓存库https://github.com/pinterest/PINCache

另外,整个逻辑的实现,还使用了ReactCocoa来简化逻辑代码,若是不是很熟悉,能够先看下:ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!

关于安全性

这个要根据本身App的状况,实际考虑下.咱们的App网络接口是基于HTTPS的,因此不存在中间人攻击的状况.因此能够保证md5和文件路径是咱们本身可控的,因此只作了最基本的md5校验.具体你们能够参考下官方的基于JSPatch的在线更新补丁实践http://jspatch.com/Docs/security:

JSPatch脚本的执行权限很高,若在传输过程当中被中间人篡改,会带来很大的安全问题,为了防止这种状况出现,咱们在传输过程当中对JS文件进行了RSA签名加密,流程以下:

服务端:

计算 JS 文件 MD5 值。
用 RSA 私钥对 MD5 值进行加密,与JS文件一块儿下发给客户端。
客户端:

拿到加密数据,用 RSA 公钥解密出 MD5 值。
本地计算返回的 JS 文件 MD5 值。
对比上述的两个 MD5 值,若相等则校验经过,取 JS 文件保存到本地。

官方有个内测的平台http://jspatch.com,来支持在线更新,可是我作的时候,是不知道的,有点重复造轮子的感受.可是,也就两天左右就实现了,只要能捋顺补丁状态控制的时机,代码自己其实并无真正的技术难点.另外,官方的内测平台,好像是闭源的,我不太敢用.

关于JS文件的编写.

能够先参考下文档https://github.com/bang590/JSPatch/wiki/defineClass使用文档,其实都是一一对应的语法转义.若是代码不少,官方还提供了转换工具:https://github.com/bang590/JSPatchConvertor,可是结果仅供参考,可能还须要二次修改.

关于 APPstore 审核

咱们的App,嵌入了JSPatch来进行Bug修复,已经经过审核,而且恰好修复了一个很紧急的Bug.这里不作过多的口水式的讨论.

小结

若是还在为每次的APP更新而提心吊胆,请细细阅读这篇文章;在线更新的,不只仅能够用来修复Bug呦~

相关文章
相关标签/搜索