JSPatch作为热更新技术的黑科技,已经不是什么前沿的新闻了,像腾讯、美团等大公司也在使用JSPatch。前段时间苹果对使用这些像JSPatch,weex等热更新技术下发警告通知或强制下架的事,技术圈里让很多小伙伴们坐不住了,闹的沸沸扬扬,这个15年就问世的框架具备很多之前的类似框架所不具备的优点,更加的小巧便捷,并且处于持续维护中,不仅如此,还由此成为了一个生态圈,bang神还为此开发了oc转js的代码转换器、可以自动提示的JSPatchX插件、以及基于这个技术的JSPatchPlatform平台。总之让大伙可以很方便的进行patch。作为技术方面的一个小探索,抱着学习的态度,初次测试一下效果。
如何混淆JSPatch热修复框架以绕过苹果的机器检测
- HotFix概述
- 集成JSPatch
HotFix概述
iOS中的HotFix方案总结详解
对于iOS,这种HotFix方案大致可以分为四种:
- WaxPatch(Alibaba)
- Dynamic Framework(Apple)
- React Native(Facebook)
- JSPatch(Tencent)
WaxPatch
WaxPatch是一个通过Lua语言编写的iOS框架,不仅允许用户使用 Lua 调用 iOS SDK和应用程序内部的 API, 而且使用了 OC runtime 特性调用替换应用程序内部由 OC 编写的类方法,从而达到HotFix的目的。
WaxPatch的优点在于它支持iOS6.0,同时性能上比较的优秀,但是缺点也是非常的明显,不符合Apple3.2.2的审核规则即不可动态下发可执行代码,但通过苹果JavaScriptCore.framework或WebKit执行的代码除外;同时Wax已经长期没有人维护了,导致很多OC方法不能用Lua实现,比如Wax不支持block;最后就是必须要内嵌一个Lua脚本的执行引擎才能运行Lua脚本;Wax并不支持arm64框架。
Dynamic Framework
动态的Framework,其实就是动态库;首先我介绍一下关于动态库和静态库的一些特性以及区别。
不管是静态库还是动态库,本质上都是一种可执行的二进制格式,可以被载入内存中执行。
iOS上的静态库可以分为.a文件和.framework,动态库可以分为.dylib(xcode7以后变成了.tdb)和.framework。
- 静态库: 链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
- 动态库: 链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
- 总结:同一个静态库在不同程序中使用时,每一个程序中都得导入一次,打包时也被打包进去,形成一个程序。而动态库在不同程序中,打包时并没有被打包进去,只在程序运行使用时,才链接载入(如系统的框架如UIKit、Foundation等),所以程序体积会小很多。
好,所以Dynamic Framework其实就是我们可以通过更新App所依赖的Framework方式,来实现对于Bug的HotFix,但是这个方案的缺点也是显而易见的它不符合Apple3.2.2的审核规则,使用了这种方式是上不了Apple Store的,它只能适用于一些越狱市场或者公司内部的一些项目使用,同时这种方案其实并不适用于BugFix,更适合App线上的大更新。所以其实我们项目中的引入的那些第三方的Framework都是静态库,我们可以通过file这个命令来查看我们的framework到底是属于static还是dynamic。
React Native
React Native支持用JavaScript进行开发,所以可以通过更改JS文件实现App的HotFix,但是这种方案的明显的缺点在于它只适合用于使用了React Native这种方案的应用。
JSPatch
JSPatch是只需要在项目中引入极小的JSPatch引擎,就可以使用JavaScript语言调用Objective-C的原生接口,获得脚本语言的能力:动态更新iOS APP,替换项目原生代码、快速修复bug。但是JSPatch也有它的自己的缺点,主要在由于它要依赖javascriptcore,framework,而这个framework是在iOS7.0以后才引入进来,所以JSPatch是不支持iOS6.0的,同时由于使用的是JS的脚本技术,所以在内存以及性能上面是要低于Wax的。
集成JSPatch
JSPatch 需要使用者有一个后台可以下发和管理脚本,并且需要处理传输安全等部署工作,JSPatch 平台帮你做了这些事,提供了脚本后台托管,版本管理,保证传输安全等功能,让你无需搭建一个后台,无需关心部署操作,只需引入一个 SDK 即可立即使用 JSPatch。
Github 开源的是 JSPatch 核心代码,使用完全免费自由,若打算自己搭建后台下发 JSPatch 脚本,可以直接使用 github 上的核心代码,与 JSPatch 平台上的 SDK 无关。JSPatch 平台的 SDK 在核心代码的基础上增加了向平台请求脚本/传输解密/版本管理等功能,只用于这个平台。
官方接入文档
注册JSPatch平台账号
1.从官网上下载提供的SDK API包来后,导入工程,在TARGETS -> Build Phases -> Link Binary With Libraries -> + 添加 libz.dylib 和 JavaScriptCore.framework
2.生成和配置RSA密钥
自定义 RSA 密钥对 RSA 密钥的作用详见安全问题。目前为了更高的安全性,平台强制要求所有补丁下发都使用自定义 RSA 密钥,生成 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
生成的公私钥,在上传布丁时要用:
用JSPatch�官网工具中提供的RSA配置工具,拖入公钥文件直接生成配置代码
注册账号成功后,在我的App中添加新应用,应用的图标生成是填写了已上架应用的Appkey,这里只是测试,就没必要了,确定之后会生成平台应用的AppKey
3.在 AppDelegate.m中按顺序调用
startWithAppKey
、
setupRSAPublicKey
、
sync
方法,可以把 [JSPatch sync] 放在 -applicationDidBecomeActive: 里,每次唤醒都能同步更新 JSPatch 补丁,不需要等用户下次启动
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/**
*AppKey:JSPatch添加应用时生成的AppKey
*RSAPublicKey:刚才生成的公钥RSA字符串
*/
[JSPatch startWithAppKey:@"834abc498b14c64b"];
[JSPatch setupRSAPublicKey:@"-----BEGIN PUBLIC KEY-----RSABLABLABLA45/44DJFJJNKSDLKS-----END PUBLIC KEY-----"];
//用来检测回调的状态,是更新或者是执行脚本之类的,相关信息,会打印在你的控制台
[JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
NSLog(@"error-->%@",error);
switch (type) {
case JPCallbackTypeUpdate: {
NSLog(@"更新脚本 %@ %@", data, error);
break;
}
case JPCallbackTypeRunScript: {
NSLog(@"执行脚本 %@ %@", data, error);
break;
}
case JPCallbackTypeCondition: {
NSLog(@"条件下发 %@ %@", data, error);
break;
}
case JPCallbackTypeGray: {
NSLog(@"灰度下发 %@ %@", data, error);
break;
}
default:
break;
} }];
[JSPatch setupDevelopment];
[JSPatch sync];
return YES;
}
4.在ViewController中创建一个laber,声明一个test方法用来给laber赋值
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) UILabel *textLaber;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.textLaber = [[UILabel alloc] initWithFrame:CGRectMake(0, 50, self.view.frame.size.width, 60)];
_textLaber.textAlignment = NSTextAlignmentCenter;
_textLaber.backgroundColor = [UIColor cyanColor];
[self.view addSubview:_textLaber];
[self test];
}
- (void)test{
self.textLaber.text = @"像疯了一样";
}
@end
5.创建main.js, 保存
console.log('run success')
defineClass("ViewController", {
test: function() {
self.textLaber().setText("内容就这样改变了");
},
})
6.就是发布布丁了,布丁文件就是上面的main.js文件,RSA密钥就是生成的,**rsa_public_key.pem **公钥文件,因为只是测试,所以勾选开发预览选项,
- 开发预览:是用来测试开发用的
- �全量下发:给所有安装布丁的人下发
- �条件下发:可以根据JSPatch设定的userId设定筛选条件下发
- 灰度下发:按人数灰度可以指定补丁对多少个用户生效,超过设置的人数后不会再生效。灰度人数可以修改增加,但不能减少,可以逐渐增加灰度人数,直到全量发布。
点击提交之后,显示发布成功
7.再看看我们的demo,�打印台收到如下消息就说明布丁更新加载成功
即使这样,你还会发现,laber的值并没有改变啊,好吧,因为补丁是先下载再生效的,所以下一次运行你才能看到效果,后续我会不断去踩坑,这是我们在main.js 中设的值
可坑能踩的坑
- 布丁脚本加载成功,却出错MD5加密之类的,当然就是你的�RSA公私钥有问题喽,重新生成一份
- JSPatch网站上的版本要一定要和工程里的一样
- label的名字别写错了
- Swift一定要在方法和属性前加dynamic,如果不是继承自NSObject的Swift类不能被动态替换
- Swift替换类和方法要比OC在类/方法名之前添加工程名
- 如果项目跑起来控制台输出没有找到文档就是网站上配置错了
相关连接:
JSPatch 基础用法
JSPatch实现原理详解:让JS调用/替换任意OC方法