JSPatch入门小试

最后编辑时间: 2018-04-09

一、前言

因为苹果的审核机制,我们修复 bug 的时候要经过如下过程

如图:

这个时间还是比较漫长的,因此热修复的出现帮助我们解决了这个问题。其中有 WaxPatch、React Native、JSPatch 等著名框架,而其中的 JSPatch 已经逐渐被各大公司所认可,我司最近也要开始使用 JSPatch 框架来助力热修复。于是我决定对它做一下整体分析与实战。为什么不叫源码解析呢?因为作者的文档写的太清楚了,而且还是中文的,我再重复一遍太班门弄斧了,所以我这里是入门小试。

二、JSPatch简介

JSPatch 是bang590大神提供的一个基于JavaScript语言的iOS平台hotfix解决方案。只需在项目引入极小的引擎,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug。

github源码地址:https://github.com/bang590/JSPatch

三、实战

3.1 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 会被触发,在控制台打印出笑脸。

如图:

ian_jspatch3

这里我们就能通过 JSPatch 去改变这个方法的执行。

不会 JavaScript 怎么办?

作者已经为我们铺好了路,不用你会JavaScript,你只要会用工具就行了。

直接借助于作者写的JSPatchConvertor进行下代码转换 传送门->点我

当然你也可以借助于JSPatchX进行手写编码 传送门->点我

最后我们一定要把写好的js代码放在js语法检查器里面检查一下 传送门->点我

如图:

ian_jspatch4

将js代码保存为 main.js 引入到项目中去。

下面将 JSPatch 的三个主要文件引入。

如图:

ian_jspatch2

在 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的方法被替换了。

如图:

ian_jspatch5

实战只是一个抛砖引玉,关于 JSPatch 的更多使用方法详见官方文档(中文的哟!)

传送门->点我

3.2 补丁的下发:

我们如何对 JSPatch 的补丁进行下发呢?

如图:

ianjspatch1

通过此流程图我们会发现,中间使用了RSA和DES加密,因为在补丁下发的时候,如果补丁被中间人拦截并修改,那么后果不堪设想,将会影响到此次涉及的所有版本的app,因此适当的加密和解密在此分发流程中必不可少。相信到了2017年随着苹果https的普及,安全性会更加完善。

当然对于这个我们也可以借助于第三方的平台,例如JSPatchbugly等。

那么我们应该自己搭建服务器还是使用第三方服务呢?我的观点还是那样,我们应该把时间浪费在有意义的事情上面。

四、原理分析

关于 JSPatch 的源码分析,作者写的已经很全面了,这里我就不都说了,贴一下作者的链接吧!

下面简单说一下我对 JSPatch 的一点理解,帮助你来去学习。

用一张图来总结下 JSPatch :

4.1 基本原理:

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 做进一步了解。

4.2 JS如何调用OC

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 有更深层的理解,欢迎大家一起交流,一起学习。
最后推荐大家两个网址:

参考引用

JSPatch-实现原理详解

IOS热更新-JSPatch实现原理+Patch现场恢复

一场站着听完的iOS技术分享会

JSPatch实现原理详解

【Dev Club 分享第四期】JSPatch 成长之路

请在下方留下您的评论.加入TG吹水群