JSPatch

为了解决因为AppStore审核而致使程序更新新版本慢,目前有两种方案实时修复线上bug:
(1)直接使用UIWebView加载网络上的HTML 的代码,这样若是有问题只须要更新服务器的HTML文件,用户从新进入程序,加载新的HTML文件,整个程序就能更新,对用户影响很是小。
(2)使用其余脚本语言经过Runtime动态调用OC
<1>WaxPatch:它把Lua脚本语言和原生Objective-C应用编程(API)结合起来,经过Lua脚原本调用OC。 
<2>JSPatch:JS经过JavaScriptCore.framework调用Runtime来实现JS调用OC.
 
  1. 简介
只须要在项目中引入极小的引擎文件,就可使用 JavaScript 调用任何 Objective-C 的原生接口,替换任意 Objective-C 原生方法。目前主要用于下发 JS 脚本替换原生 Objective-C 代码,实时修复线上 bug。
JSPatch:JS是经过JavaScriptCore.frameworkdi调用Runtime,来实现 JS调用OC。
 

例如线上 APP 有一段代码出现 bug 致使 crash:前端

@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

能够经过下发这样一段 JS 代码,覆盖掉原方法,修复这个 bug:git

//JS
defineClass("JPTableViewController", {
  //instance method definitions
  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);
    }
  }
}, {})
除了修复 bug,JSPatch 也能够用于动态运营,实时修改线上 APP 行为,或动态添加功能。
 
JSPatch 须要使用者有一个后台能够下发和管理脚本,而且须要处理传输安全等部署工做,JSPatch 平台帮你作了这些事,提供了脚本后台托管,版本管理,保证传输安全等功能,让你无需搭建一个后台,无需关心部署操做,只需引入一个 SDK 便可当即使用 JSPatch。
 
  1. JSPatch优点
(1)JS比Lua在应用开发领域有更普遍的应用,目前前端开发和前端开发有融合趋势,做为扩展的脚本语言,JS是不二之选;
(2)JSPatch更符合Apple的规则,iOS Developer Program License Agreementi里3.3.2提到不可动态下发可执行代码,但经过苹果JavaScriptCore.framework或WebKit执行的代码除外,JS正是经过JavaScriptCore.framework执行的;
(3)使用系统内置的JavaScriptCore.framework,无需内嵌脚本引擎,体积小巧;
(4)支持block。
 
 
  1. JSPatch缺点
(1)相对于WaxPatch,JSPatch劣势在于不支持iOS6,由于须要引入JavaScriptCore.framework;
目前内存的使用上会高于wax,持续改进中;
(2)存在风险:
JSPatch 让脚本语言得到调用全部原生OC方法的能力,不像web前端把能力局限在浏览器,使用上会有必定的安全风险;
<1>若在网络传输过程当中下发明文,可能会被中间人篡改JS脚本,执行任意方法,盗取APP里的相关信息,危机用户信息和APP;
<2>若下载完后JS保存在本地没有加密,在越狱的机器上用户也能够手动替换或篡改脚本
(3)风险控制:
<1>JSPatch脚本的执行权限很高,若在传输过程当中被中间人篡改,会带来很大的安全问题,为了防止这种状况出现,在传输过程当中对JS文件进行了RSA签名加密,流程以下:
服务端:计算JS文件MD5值,用RSA私钥对MD5值进行加密,与JS文件一块儿下发给客户端;
客户端:拿到加密数据,用RSA公钥解密出MD5值。本地计算返回的JS文件MD5值。对比上述的两个MD5值,若相等则校验经过,取JS文件保存到本地
因为RSA是非对称加密,再没有私钥的状况下第三方没法加密对应的MD5值,也就没法伪造JS文件,杜绝了JS文件在传输过程当中被篡改的可能。
<2>本地存储
本地存储的脚本被篡改的机会小不少,只在越狱机器上有点风险,对此JSPatch SDK在下载完脚本保存到时也进行了简单的对称加密,每次读取时解密。
 
  1. JSPatch 平台速度和稳定性如何?
经过 JSPatch 平台上传的脚本文件都会保存在 七牛云存储上,客户端 APP 只跟七牛服务器通信,支持高并发,CDN分布全国,速度和稳定性有保证。
 
 
  1. SDK接入
(1)得到AppKey
(2)集成SDK:
<1> 经过 cocoapods 集成:
在podfile中添加命令:
pod 'JSPatchPlatform'
再执行  pod install 便可。
<2>手动集成
下载 SDK 后解压,将  JSPatchPlatform.framework 拖入项目中,勾选 "Copy items if needed",并确保 "Add to target" 勾选了相应的 target。
添加依赖框架:TARGETS -> Build Phases -> Link Binary With Libraries -> + 添加  libz.dylib 和  JavaScriptCore.framework。
(3)运行
在  AppDelegate.m 里载入文件,并调用  +startWithAppKey: 方法,参数为第一步得到的 AppKey。接着调用  +sync 方法检查更新。例子:
 
#import <JSPatchPlatform/JSPatch.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     [JSPatch startWithAppKey:@"你的AppKey"];
     [JSPatch sync]; ...
}
@end
 
至此 JSPatch 接入完毕,下一步能够开始在后台为这个 App  添加 JS 补丁文件了。
 
 
  1. 常见问题
若使用 XCode8 接入,须要在项目 Capabilities 打开 Keychain Sharing 开关,不然在模拟器下载脚本后会出现  decompress error, md5 didn't match 错误(真机不管是否打开都没问题):
 
 
 
  1. SDK API
<1>  +startWithAppKey:
传入在平台申请的 appKey,启动 JSPatch SDK。同时会自动执行已下载到本地的 patch 脚本。建议在  -application:didFinishLaunchingWithOptions: 开头处调用。
 
<2>  +sync
与 JSPatch 平台后台同步,询问是否有 patch 更新,若是有更新会自动下载并执行。
!!注意  +startWithAppKey: 并不会询问后台 patch 更新,必须调用  +sync 方法。
每调用一次  +sync 就会请求一次后台,对于实时性要求不高的 APP,只需在  -application:didFinishLaunchingWithOptions: 处调用一次,这样用户会在启动时去同步 patch 信息。对于实时性要求高的 APP,能够在  -applicationDidBecomeActive: 处调用这个接口,这样会在每次用户唤醒 APP 时去同步一次后台,请求次数会增多,但有 patch 更新时用户会及时收到。
 
<3>  +setupLogger:
JSPatch SDK 里会打一些请求和执行的log,默认是以  NSLog() 打出,若你的 APP 有本身的日志系统,但愿把 log 打在你的日志系统里,能够在调用 +startWithAppKey 以前调用这个接口:
[JSPatch setLogger:^(NSString *msg) {
    //msg 是 JSPatch log 字符串,用你自定义的logger打出
    YOUR_APP_LOG(@"%@", msg);
}];
 
<4>  +testScriptInBundle
用于发布前测试脚本,调用后,会在当前项目的 bundle 里寻找 main.js 文件执行。注意不能同时调用  +startWithAppKey: 方法,测试完成后须要删除。
 
<5>  +setupCallback:
JSPatch 执行过程当中的事件回调,在如下事件发生时会调用传入的 block:
typedef NS_ENUM(NSInteger, JPCallbackType){
    JPCallbackTypeUnknow        = 0,
    JPCallbackTypeRunScript     = 1,    //执行脚本
    JPCallbackTypeUpdate        = 2,    //脚本有更新
    JPCallbackTypeUpdateDone    = 3,    //已拉取新脚本
    JPCallbackTypeCondition     = 4,    //条件下发
    JPCallbackTypeGray          = 5,    //灰度下发
};

举例:github

[JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
    switch (type) {
        case JPCallbackTypeUpdate: {
            NSLog(@"updated %@ %@", data, error);
            break;
        }
        case JPCallbackTypeRunScript: {
            NSLog(@"run script %@ %@", data, error);
            break;
        }
        default:
            break;
    }
}];
 
<6>  +setupUserData:
定义用户属性,在  +sync: 以前调用,用于条件下发,例如: 
[JSPatch setupUserData:@{
    @"userId": @"100867", 
    @"location": @"guangdong"
}];
 
<7>  +setupRSAPublicKey:
自定义 RSA key,在  +sync: 以前调用,详见  自定义 RSA 密钥
 
<8> +setupDevelopment
开发预览模式,下发补丁时若选择开发预览模式下发,只会对调用过  +setupDevelopment的客户端生效。
建议在  DEBUG 时设置,详见  开发预览
 
 
  1. 使用范例
JSPatch 能够替换线上原生代码,达到实时修复 bug 的目的,接下来举个例子说明若要通过 JSPatch 平台下发 JS 脚本修复 bug,须要进行哪些步骤。

首先项目必须接入 JSPatch SDK,并关联 AppKey,线上版本必须带有这个 SDK。web

假设已接入 JSPatch SDK 的某线上 APP 发现一处代码有 bug 致使 crash:编程

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

上述代码中取数组元素处可能会超出数组范围致使 crash,对此咱们写了以下 JS 脚本准备替换上述方法修复这个 bug:数组

//main.js
defineClass("XRTableViewController", {          
     tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
      var row = indexPath.row()
     if (self.dataSource().length > row) {//加上判断越界的逻辑
     var content = self.dataArr()[row];
     var controller = XRViewController.alloc().initWithContent(content);
   self.navigationController().pushViewController(controller);                    }
}
})

注意在 JSPatch 平台的规范里,JS脚本的文件名必须是 main.js。接下来就看如何把这个 JS 脚本下发给全部用户。浏览器

测试

在上线以前须要对脚本进行本地测试,看看运行是否正常。SDK 提供了方法 +testScriptInBundle 用于发布前的测试:七牛云存储

#import <JSPatch/JSPatch.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {                                    
[JSPatch testScriptInBundle];
….
}
@end

调用这个方法后,JSPatch 会在当前项目的 bundle 里寻找 main.js 文件执行,效果与最终线上用户下载脚本执行同样,测试完后就能够准备上线这个脚本。缓存

注意 +testScriptInBundle 不能与 +startWithAppKey: 一块儿调用,+testScriptInBundle 只用于本地测试,测试完毕后须要去除。安全

添加版本

进入 JSPatch 平台后台,在个人 APP 里选择这个 APP,点击添加版本。填入当前线上 APP 的版本号,能够在项目 TARGETS -> General -> version 上能够找到:

注意这里版本号必须一致,JSPatch 平台会只针对这个版本号下发对应的 JS 脚本,若版本号对应不上,客户端也就请求不到相应的 JS 脚本。

添加JS脚本

点击进入刚添加的版本,上传 main.js 便可。

上传能够直接全量下发,也能够选择 开发预览 或 灰度或条件下发,也可使用自定义 RSA key 对脚本进行加密签名。

上传完成后,对应版本的 APP 会请求下载这个脚本保存在本地,之后每次启动都会执行这个脚本。至此线上 bug 修复完成。

修改/删除JS脚本

若后续须要对这个脚本进行修改,能够从新上传新的脚本,APP 客户端会在请求时发现脚本已更新,下载最新脚本覆盖原来的,下次启动时执行。

 
若想直接取消某个 APP 版本的 JS 脚本补丁,能够直接在 APP 版本界面删除此 APP 版本,APP 客户端会在请求时发现脚本已被删除,即刻删除本地 JS 脚本文件,下次启动时再也不加载。
 
 
 
  1. 接入扩展
JSPatch 经过扩展实现 C 函数调用 / GCD / 锁 等功能,能够在  JSPatch github 项目 上看到。JSPatch 平台 SDK 默认没有接入这些扩展,若要使用这些功能,须要另外接入。
 
经过 cocoapods 接入
JSPatch 平台在 cocoapods 有三个 subspec,分别是: <1>JSPatchPlatform/Extensions
包含了  扩展 里根目录的文件,包括:
JPDispatch: 提供完整GCD接口  
JPLocker: 提供@synchronized接口  
JPNumber: 包装 NSNumber  
JPProtocol: 提供@protocol接口  
JPSpecialInit: 特殊类 UIWebview 和 NSCalendar 的初始化
<2>JSPatchPlatform/JPCFunction
提供调用任意 C 函数的接口,详见  C 函数调用
<3>JSPatchPlatform/JPCFunctionBinder
提供了一些经常使用 CoreFoundation C 函数接口的转接。
能够在 podfile 里直接接入:
pod 'JSPatchPlatform'
pod 'JSPatchPlatform/Extensions'
pod 'JSPatchPlatform/JPCFunction'
而后执行  pod install 即完成接入。
 
手动接入
若没有使用 cocoapods,能够在  JSPatch github 项目 下载相应的扩展文件,拖入项目。
 
 
  1. 安全问题
传输安全
JSPatch脚本的执行权限很高,若在传输过程当中被中间人篡改,会带来很大的安全问题,为了防止这种状况出现,咱们在传输过程当中对JS文件进行了RSA签名加密,流程以下:

服务端:

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

客户端:

  1. 拿到加密数据,用 RSA 公钥解密出 MD5 值。
  2. 本地计算返回的 JS 文件 MD5 值。
  3. 对比上述的两个 MD5 值,若相等则校验经过,取 JS 文件保存到本地。
 
因为 RSA 是非对称加密,在没有私钥的状况下第三方没法加密对应的 MD5 值,也就没法伪造 JS 文件,杜绝了 JS 文件在传输过程被篡改的可能。
本地存储
本地存储的脚本被篡改的机会小不少,只在越狱机器上有点风险,对此 JSPatch SDK 在下载完脚本保存到本地时也进行了简单的对称加密,每次读取时解密。
 
 
 
  1. 自定义RSA秘钥
对 RSA 密钥的做用详见 安全问题
客户端和 JSPatch 后台默认有一对 RSA 密钥,默认会用这对密钥进行加解密验证。
若对安全要求较高,能够按如下步骤自定义 RSA 密钥:
<1>生成 RSA 密钥
在 Mac 终端上执行  openssl,再执行如下三句命令,生成 PKCS8 格式的 RSA 公私钥,执行过程当中提示输入密码,密码为空(直接回车)就行。
openssl >
genrsa -out rsa_private_key.pem 1024
pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM –nocrypt
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
 
这样在执行的目录下就有了  rsa_private_key.pem 和  rsa_public_key.pem 这两个文件。这里生成了长度为 1024 的私钥,长度可选 1024 / 2048 / 3072 / 4096 ...。
<2>SDK 设置 RSA Public Key
客户端接入 SDK 后调用  +setupRSAPublicKey: 设置自定义的 RSA Public Key,注意应该在  +sync 以前调用,由于  +sync 可能会下载到脚本,这时已经要用 RSA key 去验证了。
Public Key 以字符串的方式传入,注意换行处要手动加换行符 \n,例:
//rsa_public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApgeqKYKPVFk1dk2JGrKv
EaSqqXxU2S1x32xn2M2jWK/lz7YOPRFcPhH8UgBgpUQGqbW2ooOrtlE0Ur6WHOgZ
HvozA71xKEgpQhLbX8ourcyC638zfEQJ3aUezjy5ADzlIAWr3ayBYmLBYj4OkRRG
bffxwA+i16jNVFWJFzgCrRs44cpn+nX0VsNrNjntt59J3xIhMGE+eQ2K9WDwYmv4
sw8+3MsW++z2Uornmi9v2atZnBKd/dBsGz05d++NBks7b2ot/TAiMRnit+VNTZrs
1rYQOcoCJlMUK4GDkK6bdKAPfVcD5vy2PAxDA84P2txcSkFozmZABcVvSyASB6Bn
MQIDAQAB
-----END PUBLIC KEY-----
[JSPatch setupRSAPublicKey:@"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApgeqKYKPVFk1dk2JGrKv\nEaSqqXxU2S1x32xn2M2jWK/lz7YOPRFcPhH8UgBgpUQGqbW2ooOrtlE0Ur6WHOgZ\nHvozA71xKEgpQhLbX8ourcyC638zfEQJ3aUezjy5ADzlIAWr3ayBYmLBYj4OkRRG\nbffxwA+i16jNVFWJFzgCrRs44cpn+nX0VsNrNjntt59J3xIhMGE+eQ2K9WDwYmv4\nsw8+3MsW++z2Uornmi9v2atZnBKd/dBsGz05d++NBks7b2ot/TAiMRnit+VNTZrs\n1rYQOcoCJlMUK4GDkK6bdKAPfVcD5vy2PAxDA84P2txcSkFozmZABcVvSyASB6Bn\nMQIDAQAB\n-----END PUBLIC KEY-----"];
<3>使用 Private Key 下发脚本

下发脚本时在发布脚本界面勾选 使用自定义RSA Key 选项,会出现文件上传框,选择本地的 rsa_private_key.pem 文件,与脚本一同上传,JSPatch 平台会使用这个上传的 Private Key 对脚本 MD5 值进行加密,再下发给客户端。若客户端通过上述第二步设置了对应的 Public Key,就会用设置的 Public Key 对脚本进行验证,验证经过后运行脚本,不然不会运行。

 
这里上传的  rsa_private_key.pem 只是一次性使用,不会保存在服务端,因此只有经过用户本身保存的  rsa_private_key.pem 文件才能够针对 APP 下发脚本,即便 JSPatch 平台或者七牛云被黑,第三方也没法对你的 APP 下发恶意脚本(能够下发,但验证不过,不会执行),保证安全性。 rsa_private_key.pem 请妥善保管,避免泄露。
 
 
  1. 开发预览
从 SDK 1.4 开始支持在发布脚本时先针对开发版本下发,测试无问题再进行全量下发或灰度/条件下发。
首先在代码中开启开发模式,在  +sync 以前调用  setupDevelopment 方法,建议只在 debug 模式下开启:
[JSPatch startAppWithKey:@""];
#ifdef DEBUG
[JSPatch setupDevelopment];
#endif
[JSPatch sync];
 
接着在发布补丁时选择  开发预览,就能够在 debug 模式下测试这个补丁。测试完成后能够选择全量下发或灰度/条件下发,下发给现网用户。
注:若已发布的 APP 接入了旧的 SDK,也能够经过这种方式进行测试。发布脚本时选择开发预览,则这个新版本只会在接入 SDK 1.4 并开启开发模式时生效,对接入旧 SDK 无影响,测试完成后全量下发才会生效。
 
 
  1. 灰度与条件下发
从  SDK 1.2 版本开始支持脚本的灰度与条件下发。
灰度
在后台发布补丁脚本时能够选择灰度下发,并选择灰度用户百分比,这个补丁就会按选定的百分比只对这个比例的用户起做用。例如选择灰度 30%,那这个补丁脚本只会在全部接入的设备中随机挑选 30% 的设备生效。
灰度下发无需 SDK 额外设置,只需接入的 SDK 版本在 1.2 以上。
灰度发布后后续能够修改这个灰度值,便于逐渐增长灰度数量,直到全量发布。
 
条件下发
在后台发布补丁脚本时能够选择条件下发,而后填入条件语句,只有知足条件的设备才会执行这个补丁脚本,条件语句由 key/value/运算符组成,示例: userId==10000876iOS>9.0&&isMale==1

条件语句里用到的 key/value 须要事先在 APP 里经过 +setupUserData: 设置,支持设置多个字段,用 NSDictionary 表示,例如能够设置当前登陆的用户ID以及性别:

//_userId = @"1000876"
//_isMale = @(1)
[JSPatch setupUserData:@{@"userId": _userId, @"isMale": _isMale}];

这样在下发脚本时填入条件 userId==1000876 后,这个脚本就只对这个用户生效,若是填入 isMale==0 则对这个用户不生效,对其余在 SDK 设置了 @"isMale": @"0" 的用户生效。

条件语句规则

  1. 支持符号 && || == != >= <= > <,意思跟程序里同样。
  2. 用比较符号时 >= <= > < 会把值转为数值进行对比。例如 userId>200000,即便客户端调用 +setupUserData: 接口时设置的 userId 字段是字符串,也会转为数值进行对比。
  3. 使用 == != 符号时,会以字符串形式判断是否相等,例如 1.0 == 1 结果是 NO。
  4. 等式的值不须要引号,字符串也不须要,例如:location!=guangdong
  5. 支持多个条件,例如:userId!=31242&&location==guangdong&&name==bang
  6. 若多个条件里同时有 && 和 ||&& 的优先级较高。例如 userId<200000||location==guangdong&&name==bang,会先分别计算 userId<200000 和 location==guangdong&&name==bang 的结果,再进行 || 运算。
 
条件更新规则
在发布脚本时用条件下发后,发布后能够不断修改条件,但在以前已经命中了条件执行了脚本的设备,不会由于修改条件后变为不命中,也就是说已经命中过条件执行了脚本的设备,不会再被条件的更新影响。举个例子:

在发布脚本1时设条件为 userId==1000876,某设备A设置了 @{@"userId": @"1000876"}命中了这个条件,执行了这个脚本1。设备B设置了 @{@"userId": @"2000876"} 没有命中。

接着在后台修改条件为 userId>=2000000 ,设备A并不符合这个条件,但由于以前的条件命中过,因此设备A不会再受这个改变影响,继续执行脚本1。设备B命中了这个条件,也执行了脚本1。

此外若想撤销条件全量发布,提交空条件便可。

 
内置信息
除了用户手动设置的 userData,SDK 里还内置了两个信息可供条件判断: iOS 和  isPad,分别表示 iOS 版本号和是否iPad,不须要设置就能够拿这两个字段用于条件判断。

例如只针对 iOS8 的 iPad 下发,能够直接写这个条件:iOS>=8.0&&iOS<9.0&&iPad==1

注意 iOS 版本号只会精确到两位,例如 9.2.1 会记录成 9.2,iOS==9.2 会命中 9.2.x 版本。

注意事项

  1. +setupUserData: 接口要在 +sync: 接口以前调用。
  2. 对于 SDK 1.1 及如下版本会无视任何条件和灰度值,直接全量接收。
 
 
 
  1. 在线参数
JSPatch 平台附带了在线参数功能,能够直接向 APP 下发多个固定的参数,对APP进行动态配置。
使用
在每一个 APP 侧边栏的在线参数入口进入,新增你想要下发的参数名和参数值,例如  参数名:name  参数值:bang
APP 端在  +application:didFinishLaunchingWithOptions: 里调用  +updateConfigWithAppKey: 方法,传入  appKey,APP 就会在调用处发请求获取刚才设置的在线参数。
获取成功后,能够经过  +getConfigParams 拿到全部参数,也能够经过  +getConfigParam:接口拿到单个参数,例如:
NSDictionay *configs = [JSPatch getConfigParams];
//configs == @{@"name": @"bang"}

NSString *name = [JSPatch getConfigParam:@"name"];
//name == bang
 
设置
若是想在  +updateConfigWithAppKey: 的请求返回时进行一些操做,能够经过  + setupUpdatedConfigCallback: 接口设置 callback:
[JSPatch setupUpdatedConfigCallback:^(NSDictionary *configs, NSError *error) {
    NSLog(@"%@ %@", configs, error);
}];

为了不重复请求浪费资源,默认 +updateConfigWithAppKey: 接口请求时间间隔至少为30分钟,也就是30分钟内屡次调用 +updateConfigWithAppKey: 只会请求一次。若想 APP 对在线参数响应更实时,能够经过 +setupConfigInterval: 接口修改这个间隔值。

注意
  1. 在线参数功能与 JSPatch 脚本下发功能独立,互不影响。
  2. 在线参数的计费方式一样按请求次数计算,每调用一次 +updateConfigWithAppKey: 方法算一次请求。
 
 
 
  1. 实时监控(new)
标准版用户能够看到补丁下发的实时监控信息。(须要接入 SDK 1.6 以上版本)

 
实时监控能够看到当前补丁下发成功的数量,以图表方式展现过去一天里每一个小时的累计成功数,或过去七天里天天的成功数,帮你实时掌握补丁应用状况。
当有新的补丁下发时,若客户端拉取补丁失败,会自动在下次调用  [JSPatch sync] 时从新拉取,重试 3 次失败后,会上报失败数据,在实时监控这里也能够看到每一条失败数据以及对应的错误码,方便排查问题。
接入 SDK 1.6 或以上版本后监控信息会自动发送,无需配置。监控信息只包括 补丁应用成功 以及 补丁应用失败错误码 这两种信息,不会上报其余信息。
 
 
  1. CLI API
JSPatch 平台提供了API接口,能够直接用命令行上传和修改补丁,方便开发者集成到本身的自动化发布脚本上。
上传补丁
API
POST http://jspatch.com/Apps/uploadPatch
@params email 登陆邮箱
@params password 登陆密码
@params appKey APP惟一键值
@params appVersion APP版本号
@params gray (可选)灰度策略,值为1-9,表明10%-90%
@params condition (可选)条件下发
@params patch[] 补丁文件
@params rsaKey (可选)rsa private密钥文件

//失败返回
@return {errMsg: ''}

//成功返回
@return {succ: 1, patchVersion: {$patchVersion}}
示例
在命令行经过 curl 上传补丁:
curl -F 'email=test@qq.com' -F 'password=test1234' -F 'appKey=2ba21d234fa69915' -F 'appVersion=2.0' -F 'gray=4' -F 'patch[]=@main.js' http://jspatch.com/Apps/uploadPatch
修改补丁
上传补丁时,若使用了灰度策略或条件下发,可使用这个API修改灰度值和条件值。
API
POST http://jspatch.com/Apps/updatePatch
@params email 登陆邮箱
@params password 登陆密码
@params appKey APP惟一键值
@params appVersion APP版本号
@params gray (可选)修改灰度策略,值为1-9,表明10%-90%
@params condition (可选)修改条件下发规则
@params all (可选)修改成全量下发

//失败返回
@return {errMsg: ''}

//成功返回
@return {
  succ: 1, 
  patch: {
    patchID: 5804,
    gray: 3,
    condition:null,
    isDev:0
  }
}
示例
在命令行经过 curl 修改补丁:
curl -F 'email=test@qq.com' -F 'password=test1234' -F 'appKey=2ba21d234fa69915' -F 'appVersion=2.0' -F 'condition=userId=21' http://jspatch.com/Apps/updatePatch
 
 
 
  1. 常见问题
为何脚本没有生效?
首先请确保按 使用范例的步骤作了。若出现问题,能够参考如下调试思路:
(一) 肯定 JS 脚本书写正确
首先确保没有 JS 语法错误,能够用一些 在线工具检测写的 JS 脚本有没有问题。
请按照  github wiki 里的规则写 JSPatch 脚本,几个常犯的错误: 
  1. 不能用 NSLog('xx'),应该用 console.log('xx')
  2. get property 记得加括号,例如 self.navigationItem(),而不是 self.navigationItem
  3. 私有成员变量要用 self.valueForKey() 和 self.setValue_forKey() 接口存取。
  4. block 里不能直接使用 self
其余更多请参见 wiki 的  基础用法 和  常见问题
先在本地用  +testScriptInBundle 接口执行脚本看有没有问题,(详情参照 使用范例),若没达到预期效果,能够一步步调试,第一步请在  main.js 开头打  console.log('run success'),肯定 XCode 控制台有输出这条 log,肯定脚本有被执行到,再进行其余调试。通常调试使用  console.log() 就足够,如有更多需求能够用  Safari断点调试
 
(二) 检查脚本上传下载
先确保  appKey 和  版本号 没有错误。
如有问题,看控制台输出的 log,JSPatch SDK 默认会打一些请求和执行的 log:
2016-04-27 19:04:42.212 ... JSPatch: runScript
2016-04-27 19:04:42.399 ... JSPatch: evaluated script, length: 28
打出这两句 log 表示执行了脚本,length 表示脚本的大小。
--
2016-04-27 19:04:42.399 ... JSPatch: request http://7xkfnf.com1.z0.glb.clouddn.com/6d2fddf24c5d8af2/1.0?v=1461755082.399732
2016-04-27 19:04:42.621 ... JSPatch: request success {
    v = 2;
}

这两句表示请求到了当前版本补丁版本号,这里 url 里的 6d2fddf24c5d8af2 是 appkey,后续跟的 1.0 是 App 版本号,能够检查下这两个值是否正确。若 url 不正确或者脚本没有正确上传,这里会返回 error = "Document not found"

--

2016-04-27 19:09:43.798 ... JSPatch: updateToVersion: 2
2016-04-27 19:09:43.798 ... JSPatch: request file http://7xkfnf.com1.z0.glb.clouddn.com/6d2fddf24c5d8af2/1.0/file2
2016-04-27 19:09:43.900 ... JSPatch: request file success, data length:3072
2016-04-27 19:09:43.908 ... JSPatch: updateToVersion: 2 success

这几句表示检测到的补丁版本号比本地版本更新,去下载补丁文件,下载后会当即执行,到这一步应该就没问题了。若这个版本的补丁以前已经下载过,就不会再下载。

 
另外在 SDK 1.3.1 及以前的版本,若按这个流程下发补丁:  新建 APP 版本 -> 上传补丁 -> 删除APP版本 -> 新建同一个APP版本 -> 上传补丁,会中缓存逻辑,致使请求到的脚本是删除 APP 以前上传的旧补丁。这时只须要再上传一次补丁更新版本号就能够了。
有问题照上述点一个个检查,就能够解决了,这里会尽可能列出可能会出现的问题,平台也会持续改进易用性。若还有问题,请查看  平台文档 以及  JSPatch 文档
 
出现 decompress error
若使用 XCode8 接入,须要在项目 Capabilities 打开 Keychain Sharing 开关,不然在模拟器下载脚本后会出现  decompress error, md5 didn't match 错误(真机不管是否打开都没问题):
相关文章
相关标签/搜索