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:

//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,    //灰度下发
};

举例:

[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。

假设已接入 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 错误(真机无论是否打开都没问题):
 
 
posted @ 2016-11-02 17:17  简简单单0  阅读(1142)  评论(0编辑  收藏  举报