一站式搞定 JSPatch 热修复

最近接触到热修复, 确实能解燃眉之急, 非常好用, 故分享给大家. 这里只讲 JSPatch, 这个是现在最热门最好用的框架, 用起来超级简单, 非常感谢 bang590 的贡献.

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

项目集成

[Github][1] 下载后, 按照[操作文档][2]操作就可以轻松集成, 摘录 bang590 Github 简要步骤如下:
[1]:https://github.com/bang590/JSPatch
[2]:https://github.com/bang590/JSPatch/blob/master/README-CN.md

  • 拷贝 JSPatch/ 目录下的三个文件 JSEngine.m / JSEngine.h / JSPatch.js 到项目里
  • #import "JPEngine.h"
  • 调用 [JPEngine startEngine]
  • 通过 [JPEngine evaluateScript:@""] 接口执行 JavaScript。
  • 直接把下面代码拷贝到 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法中即可, app 运行后只调用一次, 即每次运行 app 只更新一次 JS 修复
  • 如若要更新即时性, 可以把方法放到 - (void)applicationWillEnterForeground:(UIApplication *)application 这样每次 app 从后台进入前台, 都会拉取 JS 修复文件
// 方法一: 从网络拉回js脚本执行

[JPEngine startEngine];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:kDownloadPath]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [JPEngine evaluateScript:script];
}];

// 上面代码 kDownloadPath 换成你自己的 JS 文件地址即可
// 每次都从网络拉取, 虽然文件小, 但也受限也网络状态, 不太理想.
// 方法二: 先下载到本地, 再从本地文件夹中读取

NSURLSession *session = [NSURLSession sharedSession]; 
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:kDownloadPath] completionHandler:^(NSURL * _Nullable location,NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"location: %@", location);

// 下载任务会把下载的资源存放到临时文件夹tmp下. block结束后, 就会自动删除.
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *path = [docPath stringByAppendingPathComponent:@"demo.js"];
NSLog(@"path: %@", path);// 拷贝路径在 Finder ->前往 ->前往文件夹 可看到已下载文件

// 测试了会有缓存, 且不能把原有的 JS 文件覆盖, 故要先移除
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
    [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}

// 故把下载数据移动到document下
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:path]error:nil];
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    [JPEngine startEngine];
    [JPEngine evaluateScriptWithPath:path];
    }]; 
}];    
[task resume];

// 上面代码 kDownloadPath 换成你自己的 JS 文件地址即可

实际不需要每次都拉取, 该方法也只是暂缓措施, 下次迭代版本必须把上次 JS 修复的用原生解决, 这时需要有一个后台可以下发 JS 下载路径和管理脚本, 并且需要处理传输安全等部署工作.

JS 文件

JS 文件创建

  • 使用 xcode 创建 JS 文件


    一站式搞定 JSPatch 热修复_第1张图片
    xcode 创建 JS 文件.png
  • 使用 Sublime Text 工具创建 JS 文件, 同样后缀保存为 .js 即可

JS 语法

  • 在 defineClass 里定义 OC 已存在的方法即可覆盖, 语法如下:
defineClass(classDeclaration, [properties,] instanceMethods, classMethods)

@param classDeclaration: 字符串,类名/父类名和Protocol
@param properties: 新增property,字符串数组,可省略
@param instanceMethods: 要添加或覆盖的实例方法
@param classMethods: 要添加或覆盖的类方法

// 例如:
require('UIDevice');
defineClass("ViewController", {
    viewDidLoad: function() {
        var model = UIDevice.currentDevice().model();
        console.log(model);
        if (UIDevice.currentDevice().systemVersion().floatValue() >= 9) {
            console.log("9.0版本");
        } else {
            console.log("其他版本");
        }
        console.log("js 打印, 脚本号: 1.0, 替换实例成功");
    }
}, {
    test: function() {
        console.log("js 打印, 脚本号: 1.0, 替换类方法成功");
    }
});
  • 要替换多个方法, 都要重新写 defineClass("类名", [新增属性,], {实例方法}, {类方法}), 属性可以省略.

  • 只有类方法或者实例方法, 就留空大括号 {}, 如只需修改类方法: defineClass("类名", {}, {类方法}).

  • 在方法名前加 ORIG 即可调用未覆盖前的 OC 原方法:

  •   viewDidLoad: function() {
       self.ORIGviewDidLoad();
     },
    

- 在 JS 里面判断是否为空要判断 false

- ```java
    var url = "";
    var rawData = NSData.dataWithContentsOfURL(NSURL.URLWithString(url));
    if (rawData != null) {} //这样判断是错误的
    应该如下判断:
    if (!rawData){}
    在JSPatch.js源码里_formatOCToJS方法对undefined,null,isNil转换成了false。
  • Objective-C 里的常量/枚举/宏/全局变量不能直接在 JS 上使用
  • 更多语法见 JSPatch 基础语法, 也可以借助 JSPatch 代码转换器, 当然转换器不是万能了, 还需要自己细心检查.
  • JSPatch 替换的是整个方法, 哪怕只有一行代码需要修复, 整个方法都需要重写成 JS 代码. 倡导使用敏捷开发的思想, 类似于主逻辑或者是功能模块入口的方法可以抽的更细, 这样即使需要修改, 成本也不会太大.

版本管理

公司搭建后台

自己公司搭建后台, 除了下发拉取 JS 的地址外, 还可以加入一些参数, 比如: 版本控制, 指定修复某 iOS 版本等等, 条件根据需求定, 跟一般请求无异, 就不叙述了.

七牛云平台

JS 文件也可以存放到七牛云上, 七牛云同样提供版本控制, 这样自己公司后台省很多事, 只需写一个接口, 而且有一定的免费额度, 足够用了.

一站式搞定 JSPatch 热修复_第2张图片
七牛云平台.png
七牛云使用流程
  • 注册完七牛云账号后, 点击添加对象存储创建储存空间, 访问控制注意选公开空间, 这样外界才能访问到 JS 文件.
一站式搞定 JSPatch 热修复_第3张图片
添加对象存储.png
一站式搞定 JSPatch 热修复_第4张图片
创建储存空间.png
一站式搞定 JSPatch 热修复_第5张图片
内存管理.png
  • 上传文件后, 复制外链接就是 JS 文件路径
一站式搞定 JSPatch 热修复_第6张图片
上传文件.png
  • 需要注意的是七牛云平台文件是有缓存的, 所以在上传 JS 文件的命名不要和前面重复, 不然下发后看到结果会是上一次同名文件效果, 缓存时间可以在空间设置里设置
一站式搞定 JSPatch 热修复_第7张图片
文件缓存时间.png

JSPatch 平台

不想搭建后台, 可以使用 JSPatch 平台, 也不用把 JS 文件上传到七牛云, 直接上传到 JSPatch 平台即可, 功能很多, 还提供条件下发, 平台文档介绍已经非常详细了, 这里就不再赘述了.

不过平台是需要收费的

一站式搞定 JSPatch 热修复_第8张图片
JSPatch平台收费.png
!!!使用 JSPatch 平台注意点
  • 注意在 JSPatch 平台的规范里,JS 脚本的文件名必须是 main.js
  • 自定义 RSA 密钥, 按照提示在终端输入命令后, 生成的文件在主目录下:
一站式搞定 JSPatch 热修复_第9张图片
RSA 文件.png
  • 按照文档那样导入 public_key 太麻烦了, 而且容易出错, 可以把 rsa_public_key.pem 文件拖入工程中, 再执行下面代码就可以:

NSString *keyPath = [[NSBundle mainBundle] pathForResource:@"rsa_public_key" ofType:@"pem"];
NSString *publicKey = [NSString stringWithContentsOfFile:keyPath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"publicKey: %@", publicKey);
[JSPatch setupRSAPublicKey:publicKey];
//下方是 JSPatch 启动代码
[JSPatch startWithAppKey:@"19ed6339k440fa3ab"];

ifdef DEBUG

[JSPatch setupDevelopment];

endif

[JSPatch sync];

    

######集成错误录

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

- pod JSPatch 平台 SDK 完成并添加依赖库, 启动 `startWithAppKey` 和 `sync` 后报错

- ```objc
duplicate symbol _OBJC_METACLASS_$_JPEngine in:
    /Users/issuser/Library/Developer/Xcode/DerivedData/ViewController-ajurqnqqgeaehfajwnvxgpblrcmz/Build/Intermediates/ViewController.build/Debug-iphonesimulator/ViewController.build/Objects-normal/x86_64/JPEngine.o
    /Users/issuser/Library/Developer/Xcode/DerivedData/ViewController-ajurqnqqgeaehfajwnvxgpblrcmz/Build/Products/Debug-iphonesimulator/JSPatch/libJSPatch.a(JPEngine.o)
ld: 11 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

 //原因是工程里有手动导入 JSPatch.h JSPatch.m 和 JSPatch.js 文件, 和 cocoapods 冲突了

小结

JSPatch 热修复集成简单吧, 难点在 JS 语法上, 没有语法提示, 写的时候更要细心.
如果没有效果的话, 检查 JS 语法是否正确, 也可以通过 Safari 的调试工具对 JS 进行断点调试, 详见 JS 断点调试, 还有是否执行之前缓存的文件.

你可能感兴趣的:(一站式搞定 JSPatch 热修复)