JSPatch - 基本使用和学习

介绍

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(0, 1);

// 方法名包含下划线‘_‘,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:20, y:20, width:100, height:100});
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(0, 0, 200, 50);
    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(0, 0, 100, 30)];
    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 = 0; i < 20; i ++) {
            data.push("通过JS创建的Cell" + i);
            }
            self.setProp_forKey(data, ‘data‘)
            return data;
            },
            numberOfSectionsInTableView: function(tableView) {
            return 1;
            },
            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(0, "cell")
            }
            cell.textLabel().setText(self.dataSource().objectAtIndex(indexPath.row()))
            return cell
            },
            tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
            return 60
            },
            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停止维护了,也不会产生严重影响

原文地址:https://www.cnblogs.com/gongyuhonglou/p/9184260.html

时间: 2024-11-06 17:53:15

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

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

JSPatch ios开发面临审核周期长,修复bug延迟等让人无奈的问题,所以,热修复的产生成为必然. ios上线APP产生bug,需要及时修复,如何修复: 我整理了jspatch的使用说明,并建立一个简单demo供他人使用和学习,此博客不做详细介绍,具体如何使用附上代码地址: 代码下载地址: https://github.com/niexiaobo/JSPatchUse ##### demo.js里添加代码:1.重写crashBtnClick方法 2.跳转新建的JPTableViewContr

JSPatch学习笔记

JSPatch 做到了让JS调用/替换任意OC方法,让iOS APP具备hotfix的能力 1.引入执行js脚本 [JPEngine startEngine]; 直接执行JS脚本 [JPEngineevaluateScript:@"\ console.log('call JPEngine success');\ "]; 执行本地JS文件demo.js脚本 NSString *sourcePath = [[NSBundlemainBundle] pathForResource:@&quo

JSPatch学习记

本文参考JSPatch wiki :https://github.com/bang590/JSPatch/wiki 1.概念 JSPatch是一个轻量的JS引擎,能够使用JavaScript语言来调用任何object-c接口,替换任何原生的方法.目前主要用于发步JS脚本替换原生Objective-C代码,实时修复线上bug 2.原理 利用OC语言的动态性,动态的修改类的方法和属性.在app启动的时候加载我们写好的JavaScript文件并通过JavaScriptCore来执行,用JS写好的类函数

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

react_native_banner-min.png React Native(以下简称RN)有大量前端开发者的追捧.前端开发是一个活跃的社区,一直尝试着一统前后端,做一个全栈开发,RN就是他们在客户端领域的尝试. 说是从零开始,但其实我还是懂一点点JS代码的,而且算是一个有经验的iOS.Android开发,对很多js和native交互的细节和特性还算了解,在QDaily里面也做过好多hybird的尝试,还经常用JSPatch做hotfix,总的来说,就是对hot update.插件化以及hy

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

关于HotfixPatch 在IOS开发领域,由于Apple严格的审核标准和低效率,IOS应用的发版速度极慢,稍微大型的app发版基本上都在一个月以上,所以代码热更新(HotfixPatch)对于IOS应用来说就显得尤其重要. 现在业内基本上都在使用WaxPatch方案,由于Wax框架已经停止维护四五年了,所以waxPatch在使用过程中还是存在不少坑(比如参数转化过程中的问题,如果继承类没有实例化修改继承类的方法无效, wax_gc中对oc中instance的持有延迟释放...).另外苹果对于

JavaScriptCore学习之JavaScriptCore

JavaScriptCore框架的类 JavaScriptCore框架对外暴露的类实际上非常少,这样带来的好处是API非常简单.如下图所示,只有5个类,分别是JSContext,JSValue,JSManagedValue,JSVirtualMachine,JSExport,其中最核心的是JSContext和JSValue,我们平时打交道的基本就是这两个类了. 这些类的基本介绍如下: JSVirtualMachine A JSVirtualMachine instance represents

(转)ios学习--你会遇到的runtime面试题(详)

1.了解runtime吗?是什么? 2.你怎么知道的? 3.对象如何找到对应方法去调用的 于是我总结了很多网上被问到的一些关于runtime的题目,并做了详细的回答,并在后面补充了我在学习runtime时敲的一些代码,如果想吃透runtime的朋友,可以把后面补充的内容好好看完 一.你会被问到的关于runtime笔试题: 1. runtime怎么添加属性.方法等 2. runtime 如何实现 weak 属性 3. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实

iOS消息转发学习笔记

如果深入学习ios Runtime,不得不提到消息转发,很多框架的实现都基于这一功能实现(例如JSPatch) 虽然看了很多篇关于消息转发的文章,但是理解的不是很透彻,还是自己实践一些理解能更加透彻一下. 首先我自己定义了一个MyString继承NSString @interface MyString : NSString @end @implementation MyString @end 然后创建一个MyString,通过performSelector调用MissMethod,MissMet

热修复JSPatch之实战教程

??接上篇<热修复JSPatch之接口设计>,在这篇文章主要给大家讲述一下如何快速具备热修复能力,当然了如果有人有志于把JSPatch系统的学习,甚至用JSPatch进行开发的,就没有必要听我在这里啰嗦了. 简单了解下JSPatch语法 ??我这里只介绍一些简单常用的. 1. require 在使用Objective-C类之前需要调用 require('className') : require('UIView') var view = UIView.alloc().init() 可以用逗号