JSPatch简介 – 动态更新iOS APP
2017-01-17 16:41
309 查看
1.用途
是否有过这样的经历:新版本上线后发现有个严重的bug,可能会导致crash率激增,可能会使网络请求无法发出,这时能做的只是赶紧修复bug然后提交等待漫长的AppStore审核,再盼望用户快点升级,付出巨大的人力和时间成本,才能完成此次bug的修复。使用JSPatch可以解决这样的问题,只需在项目中引入JSPatch,就可以在发现bug时下发JS脚本补丁,替换原生方法,无需更新APP即时修复bug。
2.例子
@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
上述代码中取数组元素处可能会超出数组范围导致crash。如果在项目里引用了JSPatch,就可以下发JS脚本修复这个bug:
import "JPEngine.m" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [JPEngine startEngine]; [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/bugfix.JS"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; if (script) { [JPEngine evaluateScript:script]; } }]; return YES; } @end //JS defineClass("JPTableViewController", { //instance method definitions tableView_didSelectRowAtIndexPath: function(tableView, indexPath) { var row = indexPath.row() if (self.dataSource().length > row) { //加上判断越界的逻辑 var content = self.dataSource()[row]; var ctrl = JPViewController.alloc().initWithContent(content); self.navigationController().pushViewController(ctrl); } } }, {})
这样 JPTableViewController 里的 -tableView:didSelectRowAtIndexPath: 就替换成了这个JS脚本里的实现,在用户无感知的情况下修复了这个bug。
更多的使用文档和demo请参考[github项目主页]。
3.原理
简单来说,JSPatch用iOS内置的JavaScriptCore.framework作为JS引擎,但没有用它JSExport的特性进行JS-OC函数互调,而是通过Objective-C Runtime,从JS传递要调用的类名函数名到Objective-C,再使用NSInvocation动态调用对应的OC方法。3.1基础原理
能做到通过JS调用和改写OC方法最根本的原因是 Objective-C 是动态语言,OC上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法:Class class = NSClassFromString("UIViewController"); id viewController = [[class alloc] init]; SEL selector = NSSelectorFromString("viewDidLoad"); [viewController performSelector:selector];
也可以替换某个类的方法为新的实现:
static void newViewDidLoad(id slf, SEL sel) {} class_replaceMethod(class, selector, newViewDidLoad, @"");
理论上你可以在运行时通过类名/方法名调用到任何OC方法,替换任何类的实现。所以 JSPatch 的原理就是:JS传递字符串给OC,OC通过 Runtime 接口调用和替换OC方法。
3.2方法调用
require('UIView') var view = UIView.alloc().init() view.setBackgroundColor(require('UIColor').grayColor()) view.setAlpha(0.5)
引入JSPatch后,可以通过以上JS代码创建了一个 UIView 实例,并设置背景颜色和透明度。
3.3方法替换
JSPatch 可以用 defineClass 接口任意替换一个类的方法,用一种hack方式实现。OC上,每个类都是这样一个结构体:
struct objc_class { struct objc_class * isa; const char *name; …. struct objc_method_list **methodLists; /*方法链表*/ };
其中 methodList 方法链表里存储的是Method类型:
typedef struct objc_method *Method; typedef struct objc_ method { SEL method_name; char *method_types; IMP method_imp; };
Method 保存了一个方法的全部信息,包括SEL方法名,type各参数和返回值类型,IMP该方法具体实现的函数指针。
通过 Selector 调用方法时,会从 methodList 链表里找到对应Method进行调用,这个 methodList 上的的元素是可以动态替换的,可以把某个 Selector 对应的函数指针IMP替换成新的,也可以拿到已有的某个 Selector 对应的函数指针IMP,让另一个 Selector 跟它对应,Runtime 提供了一些接口做这些事,以替换 UIViewController 的 -viewDidLoad: 方法为例:
static void viewDidLoadIMP (id slf, SEL sel) { JSValue *jsFunction = …; [jsFunction callWithArguments:nil]; } Class cls = NSClassFromString(@"UIViewController"); SEL selector = @selector(viewDidLoad); Method method = class_getInstanceMethod(cls, selector); //获得viewDidLoad方法的函数指针 IMP imp = method_getImplementation(method) //获得viewDidLoad方法的参数类型 char *typeDescription = (char *)method_getTypeEncoding(method); //新增一个ORIGViewDidLoad方法,指向原来的viewDidLoad实现 class_addMethod(cls, @selector(ORIGViewDidLoad), imp, typeDescription); //把viewDidLoad IMP指向自定义新的实现 class_replaceMethod(cls, selector, viewDidLoadIMP, typeDescription);
这样就把 UIViewController 的 -viewDidLoad 方法给替换成我们自定义的方法,APP里调用 UIViewController 的 viewDidLoad 方法都会去到上述 viewDidLoadIMP 函数里,在这个新的IMP函数里调用JS传进来的方法,就实现了替换 -viewDidLoad 方法为JS代码里的实现,同时为 UIViewController 新增了个方法 -ORIGViewDidLoad 指向原来 viewDidLoad 的IMP,JS可以通过这个方法调用到原来的实现。
4.安全部署RSA校验
RSA校验属于数字签名,用了跟 HTTPS 一样的非对称加密,只是简化了,把非对称加密只用于校验文件,而不解决传输过程中数据内容泄露的问题,而我们的目的只是防止传输过程中数据被篡改,对于数据内容泄露并不是太在意。整个校验过程如下:服务端计算出脚本文件的 MD5 值,作为这个文件的数字签名。
服务端通过私钥加密第 1 步算出的 MD5 值,得到一个加密后的 MD5 值。
把脚本文件和加密后的 MD5 值一起下发给客户端。
客户端拿到加密后的 MD5 值,通过保存在客户端的公钥解密。
客户端计算脚本文件的 MD5 值。
对比第 4/5 步的两个 MD5 值(分别是客户端和服务端计算出来的 MD5 值),若相等则通过校验。
只要通过校验,就能确保脚本在传输的过程中没有被篡改,因为第三方若要篡改脚本文件,必须计算出新的脚本文件 MD5 并用私钥加密,客户端公钥才能解密出这个 MD5 值,而在服务端未泄露的情况下第三方是拿不到私钥的。
最后有个小问题,保存在客户端的代码也可能被人篡改,需不需要采取措施?这个要看各人需求了,因为这个安全问题不大,能篡改本地文件,差不多已经有手机所有权限了,这时也无所谓脚本会不会被篡改了。若有需要,可以加个简单的对称加密,或者按上述流程每次都验证一遍MD5值。
相关文章推荐
- JSPatch-动态更新IOS APP
- JSPatch – 动态更新iOS APP
- JSPatch – 动态更新iOS APP
- JSPatch – 动态更新iOS APP
- JSPatch – 动态更新iOS APP
- 利用JSPatch跳过AppStore审核,动态更新APP
- JSPatch – 动态更新iOS APP
- iOS 动态更新方案对比:JSPatch vs React Native
- JSPatch – 动态更新iOS APP
- JSPatch – 动态更新iOS APP
- iOS 动态更新 修复bug jspatch wax总结。
- iOS 动态更新方案 JSPatch 与 React Native 的对比
- js的动态加载、缓存、更新以及复用(二)
- 前端js实现动态更新进度条(重要)
- 前端js实现动态更新进度条-7
- JSPatch – 动态更新iOS APP
- 【新人笔记16.04.12更新】JS--动态插入内容
- dynamic-css 动态 CSS 库,使得你可以借助 MVVM 模式动态生成和更新 css,从 js 事件和 css 选择器的苦海中脱离出来
- cocos2d-js 在线更新代码脚本 动态更新脚本程序 热更新 绕过平台审核 不需重新上架
- js的动态加载、缓存、更新以及复用(一)