因为苹果的审核机制,我们修复 bug 的时候要经过如下过程
如图:
这个时间还是比较漫长的,因此热修复的出现帮助我们解决了这个问题。其中有 WaxPatch、React Native、JSPatch 等著名框架,而其中的 JSPatch 已经逐渐被各大公司所认可,我司最近也要开始使用 JSPatch 框架来助力热修复。于是我决定对它做一下整体分析与实战。为什么不叫源码解析呢?因为作者的文档写的太清楚了,而且还是中文的,我再重复一遍太班门弄斧了,所以我这里是入门小试。
JSPatch 是bang590大神提供的一个基于JavaScript语言的iOS平台hotfix解决方案。只需在项目引入极小的引擎,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug。
github源码地址:https://github.com/bang590/JSPatch
我们做一个最简单的例子来初步认识一下 JSPatch 的强大!
1.首先我们创建一个Demo
然后我创建一个控制器,在上面加一个按钮 click,按钮的点击方法为”myBtnClick:”
#import "RootViewController.h"
@interface RootViewController ()
@end
@implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"JSPatchDemo";
self.view.backgroundColor = [UIColor whiteColor];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[self.view addSubview:btn];
btn.frame = CGRectMake(0, 0, 100, 100);
btn.center = self.view.center;
btn.backgroundColor = [UIColor blackColor];
btn.layer.cornerRadius = 5;
[btn setTitle:@"click" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(myBtnClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)myBtnClick:(id)sender
{
NSLog(@"?");
}
@end
此时我们点击这个按钮,myBtnClick 会被触发,在控制台打印出笑脸。
如图:
这里我们就能通过 JSPatch 去改变这个方法的执行。
不会 JavaScript 怎么办?
作者已经为我们铺好了路,不用你会JavaScript,你只要会用工具就行了。
直接借助于作者写的JSPatchConvertor进行下代码转换 传送门->点我
当然你也可以借助于JSPatchX进行手写编码 传送门->点我
最后我们一定要把写好的js代码放在js语法检查器里面检查一下 传送门->点我
如图:
将js代码保存为 main.js 引入到项目中去。
下面将 JSPatch 的三个主要文件引入。
如图:
在 AppDelegate 里引入如下代码:
- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions {
// Override point for customization after application launch.
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
RootViewController *rootViewController = [[RootViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
return YES;
}
下面我们运行Demo,会发现这个click的方法被替换了。
如图:
实战只是一个抛砖引玉,关于 JSPatch 的更多使用方法详见官方文档(中文的哟!)
传送门->点我
我们如何对 JSPatch 的补丁进行下发呢?
如图:
通过此流程图我们会发现,中间使用了RSA和DES加密,因为在补丁下发的时候,如果补丁被中间人拦截并修改,那么后果不堪设想,将会影响到此次涉及的所有版本的app,因此适当的加密和解密在此分发流程中必不可少。相信到了2017年随着苹果https的普及,安全性会更加完善。
当然对于这个我们也可以借助于第三方的平台,例如JSPatch、bugly等。
那么我们应该自己搭建服务器还是使用第三方服务呢?我的观点还是那样,我们应该把时间浪费在有意义的事情上面。
关于 JSPatch 的源码分析,作者写的已经很全面了,这里我就不都说了,贴一下作者的链接吧!
下面简单说一下我对 JSPatch 的一点理解,帮助你来去学习。
用一张图来总结下 JSPatch :
JSPatch 能通过 JS 去改写 OC 的方法主要还是通过 Runtime 实现的,我们可以利用 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, @"");
// 注册一个新类,并为此类添加方法
Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);
具体可以学习一下《Objective-C Runtime 1小时入门教程》 对Runtime 做进一步了解。
JSPatch 中是通过 require() 实现类的获取的,例如调用了 require(‘UIView’) ,那么他就会在 JS 全局作用域上创建一个 JS 形式的以 UIView 命名的对象。
示例如下:
UIView = require("UIView");var _require = function(clsName) {
if (!global[clsName]) {
global<span>[</span>clsName<span>]</span> <span>=</span> <span>{</span>
__clsName<span>:</span> clsName
<span>}</span>
}
return global[clsName]
}
然后进行方法调用,UIView.alloc()。JSPatch 将所有 JS 的方法调用都通过正则进行替换,统一调用 __c() 函数,然后再进行分发,做到了类似 OC/Lua/Ruby 等的消息转发机制。_methodFunc 方法就把要调用的类名和方法名传递给 OC 的。例如下面的转换:
如图:
代码如下:
Object.defineProperty(Object.prototype, '__c', {value: function(methodName) {
if (!this.__obj && !this.__clsName) return this[methodName].bind(this);
var self = this
return function(){var args <span>=</span> Array<span>.</span>prototype<span>.</span>slice<span>.</span><span>call</span><span>(</span>arguments<span>)</span>
<span>return</span> <span>_methodFunc</span><span>(</span><span>self</span><span>.</span>__obj<span>,</span> <span>self</span><span>.</span>__clsName<span>,</span> methodName<span>,</span> args<span>,</span> <span>self</span><span>.</span>__isSuper<span>)</span>
}
}})
_methodFunc() 就是把相关信息传给 OC ,OC 用 Runtime 接口调用相应方法,返回结果值,这个调用就结束了。
如图:
JS 和 OC 间互传消息是通过 JavaScriptCore 桥接的,OC 端在启动 JSPatch 引擎时会创建一个 JSContext 实例,JSContext 是 JS 代码的执行环境,可以给 JSContext 添加方法,JS 就可以直接调用这个方法:
如图:
_methodFunc() 中的 _OCcallI 就调用了这个方法
如图:
这只是一个入门小试,如果想对 JSPatch 有更深层的理解,欢迎大家一起交流,一起学习。
最后推荐大家两个网址: