一站式搞定 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 文件


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

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

JSPatch 平台

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

不过平台是需要收费的

JSPatch平台收费.png
!!!使用 JSPatch 平台注意点
  • 注意在 JSPatch 平台的规范里,JS 脚本的文件名必须是 main.js
  • 自定义 RSA 密钥, 按照提示在终端输入命令后, 生成的文件在主目录下:
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 断点调试, 还有是否执行之前缓存的文件.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

  • JSPatch是一个可以在线修复bug的轻量级框架,项目中嵌入这个框架可以让你的app具有热更新的能力。你可以通过...
    daixunry阅读 5,985评论 5 38
  • 一:关于JSPatch JSPatch : 是一个iOS动态更新框架,只需在项目中引入极小的引擎,就可以使用Jav...
    dahaibushen阅读 499评论 0 0
  • JSPatch是什么 JSPatch是一个开源项目,只需要在项目里引入极小的引擎文件,就可以使用 JavaScri...
    ImmortalSummer阅读 2,525评论 7 11
  • 前言 IOS热修复一直是关注的重点之一。由于appstore的审核上架机制的局限,新发布的版本往往要等待很长时间才...
    o翻滚的牛宝宝o阅读 3,296评论 17 42
  • 你说,我无感情 你说,我只是泥瓦拼 你说,我只是木头加铁钉 我说,不管风吹雨淋 不管是天晴雨阴 不管是雪积冰凌 我...
    舒己怀_Frank阅读 409评论 16 35