<commit_before>---
layout: post
title: 关于使用JSPatch几个技术点的分析与实现.
date: 2017-02-24
categories: blog
tags: [iOSDev]
description: 关于使用JSPatch几个技术点的分析与实现.

---

## 使用JSPatch几个技术点的分析与实现

基本实现原理

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


## 1.文件 md5 值的获取与校验

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

md5 文件完整路径.
关于校验md5的代码,其实最核心的是如何在oc中使用代码获取某个文件的md5值,然后进行比对.网上的示例很多,但可能不太靠谱,下面贴一段确实可行的,注意要引入系统库 
#####include <CommonCrypto/CommonDigest.h> :
-(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 @"";
    }
}
## 2.补丁状态的管理

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

/**
 *  补丁状态.
 */
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

}
## 3.补丁的增删改查

增:服务器返回的补丁,本地不存在时,会默认下载存储,并执行.
删: 服务器返回的补丁集中,不包含本地的某个补丁,则此补丁下次不会再被执行.
改: 服务器返回的补丁,本地包含,但md5值变化,此时会重新下载此补丁.
查: 会默认在应用启动时,执行所有存在,且md5值匹配的补丁.补丁集的信息,会在每次联网更新时更新.此处使用的是一个缓存库https://github.com/pinterest/PINCache
另外,整个逻辑的实现,还使用了ReactCocoa来简化逻辑代码,如果不是很熟悉,可以先看下: ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!

## 4.关于安全性

这个要根据自己App的情况,实际考虑下.我们的App网络接口是基于HTTPS的,所以不存在中间人攻击的情况.所以可以保证md5和文件路径是我们自己可控的,所以只做了最基本的md5校验.具体大家可以参考下官方的基于JSPatch的在线更新补丁实践http://jspatch.com/Docs/security :

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

## 5.服务端：

计算 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 ,但是结果仅供参考,可能还需要二次修改.
 <commit_msg>JSPatch update <commit_after>---
layout: post
title: 关于使用JSPatch几个技术点的分析与实现.
date: 2017-02-24
categories: blog
tags: [iOSDev]
description: 关于使用JSPatch几个技术点的分析与实现.

---

## 使用JSPatch几个技术点的分析与实现

基本实现原理

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


## 1.文件 md5 值的获取与校验

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

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

-(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 @"";
    }
}


## 2.补丁状态的管理

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

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



如何在本地测试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

}
## 3.补丁的增删改查

增:服务器返回的补丁,本地不存在时,会默认下载存储,并执行.
删: 服务器返回的补丁集中,不包含本地的某个补丁,则此补丁下次不会再被执行.
改: 服务器返回的补丁,本地包含,但md5值变化,此时会重新下载此补丁.
查: 会默认在应用启动时,执行所有存在,且md5值匹配的补丁.补丁集的信息,会在每次联网更新时更新.此处使用的是一个缓存库https://github.com/pinterest/PINCache
另外,整个逻辑的实现,还使用了ReactCocoa来简化逻辑代码,如果不是很熟悉,可以先看下: ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!

## 4.关于安全性

这个要根据自己App的情况,实际考虑下.我们的App网络接口是基于HTTPS的,所以不存在中间人攻击的情况.所以可以保证md5和文件路径是我们自己可控的,所以只做了最基本的md5校验.具体大家可以参考下官方的基于JSPatch的在线更新补丁实践http://jspatch.com/Docs/security :

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

## 5.服务端：

计算 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 ,但是结果仅供参考,可能还需要二次修改.
 

{'fraction_non_alphanumeric': 0.11533159748812988, 'fraction_numerical': 0.016541583703476796, 'mean_word_length': 5.376953125, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 7, 'https://': 7, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3752679', 'n_tokens_mistral': 4248, 'n_tokens_neox': 4703, 'n_words': 313}