iOS 使用JSPatch实现APP线上修复的热更新

一、什么是 JSPatch?

JSPatch 是一个开源项目( Github链接 ),只需要在项目里引入极小的引擎文件,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,替换任意 Objective-C 原生方法。目前主要用于下发 JS 脚本替换原生 Objective-C 代码,实时修复线上 bug。

例如线上 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 详细使用文档见 Github Wiki

二、什么是 JSPatch 平台?

JSPatch 需要使用者有一个后台可以下发和管理脚本,并且需要处理传输安全等部署工作,JSPatch 平台帮你做了这些事,提供了脚本后台托管,版本管理,保证传输安全等功能,让你无需搭建一个后台,无需关心部署操作,只需引入一个 SDK 即可立即使用 JSPatch。

三、JsPatch实现的内部原理

JsPatch能做到通过JS调用和改写OC方法最根本的原因是 Objective-C 是动态语言,OC上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名和方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现,还可以新注册一个类,为类添加方法。所以 JSPatch 的原理就是:JS传递字符串给OC,OC通过 Runtime 接口调用和替换OC方法。这个很容易理解,JS的作用只是一个信使的作用,具体实现还是得靠我们OC。

同时在这里给大家一个比较好的网站 点这里.这是一个OC转JS的一个工具网站。

四、SDK接入

  • 第一步 获得 AppKey

在平台上注册帐号,可以任意添加新 App,每一个 App 都有一个唯一的 AppKey 作为标识。

  • 第二步 集成SDK

通过 cocoapods 集成

在 podfile 中添加命令:

pod 'JSPatchPlatform'

再执行 pod install 即可。

手动集成

若没有使用 cocoapods,也可以手动集成。在本页左侧下载 SDK 后解压,将JSPatchPlatform.framework 拖入项目中,勾选 "Copy items if needed",并确保 "Add to target" 勾选了相应的 target。

SDK1

SDK2

添加依赖框架:TARGETS -> Build Phases -> Link Binary With Libraries -> + 添加 libz.dylib 和JavaScriptCore.framework

SDK3

注意:手动集成无法断点调试 JSPatch 核心源码,推荐使用 cocoapods 方式集成。

  • 第三步 运行

在 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 补丁文件了。


上述例子是把 JSPatch 同步放在 -application:didFinishLaunchingWithOptions: 里,若希望补丁能及时推送,可以把 [JSPatch sync] 放在 -applicationDidBecomeActive: 里,每次唤醒都能同步更新 JSPatch 补丁,不需要等用户下次启动:

#import <JSPatchPlatform/JSPatch.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [JSPatch startWithAppKey:@"你的AppKey"];
    ...
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
    [JSPatch sync];
    ...
}
@end

常见问题

若使用 XCode8 接入,需要在项目 Capabilities 打开 Keychain Sharing 开关,否则在模拟器下载脚本后会出现 decompress error, md5 didn't match 错误(真机无论是否打开都没问题):

五、使用范例

首先项目必须接入 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 上可以找到:

SDK3

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

添加JS脚本

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

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

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

修改/删除JS脚本

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

若想直接取消某个 APP 版本的 JS 脚本补丁,可以直接在 APP 版本界面删除此 APP 版本,APP 客户端会在请求时发现脚本已被删除,即刻删除本地 JS 脚本文件,下次启动时不再加载。

参考:JSPatch 平台http://blog.cnbang.net/tech/2808/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值