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

    xiaoxiao2021-03-25  139

    一、什么是 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。

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

    注意:手动集成无法断点调试 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 上可以找到:

    注意这里版本号必须一致,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/
    转载请注明原文地址: https://ju.6miu.com/read-2647.html

    最新回复(0)