介绍

JSPatch是2015年由bang推出的能实现热修复的工具,只要在项目中引入极小的JSPatch引擎,就可以用 JavaScript 调用和替换任何 Objective-C 的原生方法,获得脚本语言的能力<动态更新 APP、替换项目原生代码修复 bug>。
作者已将JSPatch商业化,提供了脚本后台托管,版本管理,保证安全传输等功能,只需引入一个SDK(+startWithAppKey:)即可使用.JSPatch Platform

实现原理

JSPatch 能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言,OC 上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,通过Apple在iOS7中发布的javeScriptCore.framework来解析JavaScript脚本,与Objective-C的代码进行桥接,利用运行时的特性,让app具备hit code push能力
具体实现原理作者已经给出,不再累赘描述,请看这里

使用方法

具体的使用方法这里有详细介绍,这里说下大概流程</br>
从github上下者JSPatch文件,将整个JSPatch文件夹拷贝到项目中,导入JPEngine.h,调用[JPEngine startEngine]开启JSPatch引擎,通过[JPEngine evaluateScript:@"..."]接口来执行JavaScript语句.个人认为JSPatch的语法不用刻意去看,在写的过程中卡壳了就去作者的原文中查找即可.

[JPEngine startEngine];

// 直接执行js
[JPEngine evaluateScript:@"\
var alertView = require('UIAlertView').alloc().init();\
alertView.setTitle('Alert');\
alertView.setMessage('AlertView from js'); \
alertView.addButtonWithTitle('OK');\
alertView.show(); \
"]; // 从网络拉回js脚本执行(实现热修复的关键)
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/test.js"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[JPEngine evaluateScript:script];
}]; // 执行本地js文件
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];

JaveScript的基础使用方式(摘自bang):

// 调用require引入要使用的OC类
require('UIView, UIColor, UISlider, NSIndexPath') // 调用类方法
var redColor = UIColor.redColor(); // 调用实例方法
var view = UIView.alloc().init();
view.setNeedsLayout(); // set proerty
view.setBackgroundColor(redColor); // get property
var bgColor = view.backgroundColor(); // 多参数方法名用'_'隔开:
// OC:NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
var indexPath = NSIndexPath.indexPathForRow_inSection(, ); // 方法名包含下划线'_',js用双下划线表示
// OC: [JPObject _privateMethod];
JPObject.__privateMethod() // 如果要把 `NSArray` / `NSString` / `NSDictionary` 转为对应的 JS 类型,使用 `.toJS()` 接口.
var arr = require('NSMutableArray').alloc().init()
arr.addObject("JS")
jsArr = arr.toJS()
console.log(jsArr.push("Patch").join('')) //output: JSPatch // 在JS用字典的方式表示 CGRect / CGSize / CGPoint / NSRange
var view = UIView.alloc().initWithFrame({x:, y:, width:, height:});
var x = view.bounds().x; // block 从 JavaScript 传入 Objective-C 时,需要写上每个参数的类型。
// OC Method: + (void)request:(void(^)(NSString *content, BOOL success))callback
require('JPObject').request(block("NSString *, BOOL", function(ctn, succ) {
if (succ) log(ctn)
})); // GCD
dispatch_after(function(1.0, function(){
// do something
}))
dispatch_async_main(function(){
// do something
})

一个使用JaveScript的简单例子:

//  AppDelegate.m

#import "AppDelegate.h"
#import "HBJSPatchMainViewController.h"
#import "JPEngine.h"
@interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[JPEngine startEngine]; //启动引擎
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; //从本地读取JS
[JPEngine evaluateScript:script]; //执行JS语句 self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = [[UINavigationController alloc]initWithRootViewController:[[HBJSPatchMainViewController alloc ]init]];
[self.window makeKeyAndVisible]; return YES;
}
@end // ViewController.m #import "HBJSPatchMainViewController.h" @interface HBJSPatchMainViewController () @end @implementation HBJSPatchMainViewController - (void)viewDidLoad {
[super viewDidLoad];
![Uploading JSPatch_353937.gif . . .] UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(, , , );
button.center = self.view.center;
button.backgroundColor = [UIColor orangeColor];
[button setTitle:@"进入JS生成的UI界面" forState:UIControlStateNormal];
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonTouch:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
self.view.backgroundColor = [UIColor blueColor]; } - (void)buttonTouch:(UIButton *)button { // 触发的方法在demo.JS中
}
@end // HBJSPatchTsetViewController.m #import "HBJSPatchTsetViewController.h" @interface HBJSPatchTsetViewController () @end @implementation HBJSPatchTsetViewController - (void)viewDidLoad {
[super viewDidLoad];
UILabel *lable = [[UILabel alloc] initWithFrame:CGRectMake(, , , )];
lable.center = self.view.center;
lable.textAlignment = NSTextAlignmentCenter;
lable.text = @"JS到原生UI";
[self.view addSubview:lable];
self.view.backgroundColor = [UIColor whiteColor]; } @end
//demo.js文件 //按钮事件
defineClass('HBJSPatchMainViewController', {//寻址到哪个类中的
buttonTouch: function(button) {//改写类中的方法
//跳转到tableView
var tableViewCtrl = HBTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)
}
}) defineClass('HBTableViewController : UITableViewController', { //创建类并实现类中的方法
dataSource: function() {
//JSPatch可以通过 -getProp:, -setProp:forKey: 这两个方法给对象动态添加成员变量。
var data = self.getProp('data')
if (data) return data;
var data = [];
for (var i = ; i < ; i ++) {
data.push("通过JS创建的Cell" + i);
}
self.setProp_forKey(data, 'data')
return data;
},
numberOfSectionsInTableView: function(tableView) {
return ;
},
tableView_numberOfRowsInSection: function(tableView, section) {
return self.dataSource().count();
},
tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("cell")
if (!cell) {
cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(, "cell")
}
cell.textLabel().setText(self.dataSource().objectAtIndex(indexPath.row()))
return cell
},
tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
return
},
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) { //跳转到原生UI
var testViewController = require('HBJSPatchTsetViewController').alloc().init()
self.navigationController().pushViewController_animated(testViewController, YES)
},
})
 
示例图

通常我们将脚本的执行放在AppDelegate的didFinishLaunchingWithOptions:方法中,个人建议用一个管理工具类(manager)来管理脚本下载和执行,可以用来设置一个脚本下载的间隔时间和对本地补丁进行检测等等

更多用法请点这里

安全问题

由于JS脚本能随意的调用OC方法,修改OC文件,权限非常大,若被人攻击替换代码,会造成较大的危害.一般我们在JS文件提交到后台后应该对其进行加密传输,这里推荐RSA和HTTPS来保证安全性,后面我会补遍加密的文章,有兴趣的请关注我,这里先推荐这遍文章:JSPatch部署安全策略

思考

  • 现在苹果appStore的审核周期已经变成一天,使得bug的修复速度变得很快,小公司还有没有必要增加人工成本来实现热修复?
  • 使用JSPatch的重中之重就是安全问题,虽然可以人为的进行加密传输,但总是有风险,而只要一出现问题都会是重大的问题
  • 在一个很复杂的方法中,仅中间某一行代码需要修改,就要将整个方法用JS重写一遍,推介作者开发的Objective-C转JavaScript代码工具JSPatch Convertor,但一些复杂的语法还是要人工修正
  • 当使用JSPatch解决线上bug,应在下个版本及时用OC代码修改bug写入项目中,不应该让补丁代码存留超过一个版本,若之后JSPatch停止维护了,也不会产生严重影响

JSPatch - 基本使用和学习的更多相关文章

  1. ios开发不能不知的动态修复bug补丁第三方库JSPatch 使用学习:JSPatch导入、和使用、.js文件传输加解密

    JSPatch ios开发不能不知的动态修复bug补丁第三方库JSPatch 使用学习:JSPatch导入.和使用..js文件传输加解密 ios开发面临审核周期长,修复bug延迟等让人无奈的问题,所以 ...

  2. JSPatch学习笔记

    本文参考JSPatch wiki :https://github.com/bang590/JSPatch/wiki 1.概念 JSPatch是一个轻量的JS引擎,能够使用JavaScript语言来调用 ...

  3. 【腾讯Bugly干货分享】JSPatch 成长之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/579efa7083355a9a57a1ac5b Dev Club 是一个交流移动 ...

  4. JSPatch使用小记

    hotfix的作用众所周知,Android和iOS都有各自的技术,但是相比Android的当天发布来说(如果你们的项目不需要灰度),iOS热更新的意义更加重大.因为iOS审核周期长不说,而且运气不好会 ...

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

    关于HotfixPatch 在IOS开发领域,由于Apple严格的审核标准和低效率,IOS应用的发版速度极慢,稍微大型的app发版基本上都在一个月以上,所以代码热更新(HotfixPatch)对于IO ...

  6. iOS学习资源个人整理

    1208更新: http://www.tuyiyi.com                                    图翼网 https://github.com/Alamofire/Al ...

  7. RN学习1——前奏,app插件化和热更新的探索

    react_native_banner-min.png React Native(以下简称RN)有大量前端开发者的追捧.前端开发是一个活跃的社区,一直尝试着一统前后端,做一个全栈开发,RN就是他们在客 ...

  8. iOS学习网站及大牛网址(实时更新)

    iOS学习网站及大牛网址(实时更新) 学习网站 https://github.com/Tim9Liu9/TimLiu-iOS  自己总结的iOS.mac开源项目及库 https://github.co ...

  9. iOS热更新技术被苹果官方警告?涉及到RN、Weex、JSPatch!!!

    今天一早,不少iOS开发群都炸窝了,原因是部分iOS开发者收到了苹果的警告邮件: 有开发者质疑可能是项目中使用了JSPatch.weex以及ReactNative等热更新技术.对于修复bug提交审核的 ...

随机推荐

  1. oracle18c linux x86-64 install 杂记

    132 yum install libstdc++-devel 133 yum install compat-libstdc++-33 135 yum install compat-libcap1 1 ...

  2. Lua中用Split函数分割字符串

    function Split(szFullString, szSeparator) local nFindStartIndex = local nSplitIndex = local nSplitAr ...

  3. Cakephp中使用JavaScriptHelper来引入js文件

    页面的head部分的内容在Cakephp中主要是有htmlhelper来进行控制的,而js部分则是由JavaScripthelper来进行控制的,在controller里面设置好:var $helpe ...

  4. Java利用for循环输出空心的菱形

    编写程序,在控制台上输出空心菱形,对角距离为6. public class Diamond { public static void main(String[] args) { printHollow ...

  5. Dubbo -- 系统学习 笔记 -- 示例 -- 泛化引用

    Dubbo -- 系统学习 笔记 -- 目录 示例 想完整的运行起来,请参见:快速启动,这里只列出各种场景的配置方式 泛化引用 泛接口调用方式主要用于客户端没有API接口及模型类元的情况,参数及返回值 ...

  6. flexbox子盒子align-self属性

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. beautifulsoup4 安装教程

    下载beautifulsoup, 下载地址:https://www.crummy.com/software/BeautifulSoup/bs4/download/ 下载完成之后,解压到一个文件夹,用c ...

  8. Maven编译出现“[ERROR] java.lang.OutOfMemoryError: Java heap space”

    Windows下添加环境变量MAVEN_OPTS的value为-Xms1024m -Xmx1024m -Xss1m Linux下可修改.profile或者.bash_profile文件,并做如下设置: ...

  9. c语言学习笔记---预编译

    专题三: 1)       预编译 处理所有的注释,以空格代替, 将所有的#define删除,并且展开所有的宏定义, 处理条件编译指令#if,#ifdef,#elif,#else,#endif 处理# ...

  10. 使用session防止表单进行重复提交

    我们都知道可以通过js的方法来实现防止表单重复提交,但是js只适用于“在网络延迟的情况下让用户有时间点击多次submit按钮导致表单重复提交” 的情况下进行操作, 那如果碰到“表单提交后用户点击[刷新 ...