JSPatch 是一款给 iOS 应用实现热更新功能所用的工具。它能够使开发者的 iOS 应用在不重新发布版本的情况下,即时修改 Objective-C 代码并生效。JSPatch 在应用非常适合对紧急问题进行修复或测试新的功能。
下面我们就来详细介绍下 JSPatch 的使用方法和案例,方便开发者更好地使用 JSPatch 实现 iOS 应用热更新。
## 一、JSPatch 的配置
### 1.1、下载 JSPatch
JSPatch 首先需要在官网下载 SDK,目前官网地址为:[http://jspatch.com/](http://jspatch.com/),或者从 GitHub 上下载:[https://github.com/bang590/JSPatch](https://github.com/bang590/JSPatch)。
SDK 有两种版本,分别是 JSPatch 和 JSPatch Pro。其中,JSPatch 是 JSPatch 的基础版本,仅支持集成 Xcode,而 JSPatch Pro 则支持更多语言和平台,如 JavaScript、Python、Lua等。
### 1.2、集成 JSPatch
在项目中添加 JSPatch 的头文件和其他库,可以通过 pod 安装也可以手动集成进项目中。
```ruby
pod 'JSPatch'
```
或者手动将 libJSPatch.a 和 JSPatch.js 两个文件拖拽到 Xcode 工程中。
然后在项目中添加下面的代码来启动 JSPatch。这段代码需要在 App 启动时执行。
```objective-c
#import "JSPatch.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[JSPatch startWithAppKey:@"your app key"];
[JSPatch testScriptInBundle];//测试是否成功
[JSPatch showDebugView];//对应的视图,用于调试
//...
return YES;
}
```
需要注意的是,JSPatch 需要一个 AppKey,AppKey 需要去 JSPatch 官网申请后才能够使用。
### 1.3、JS 脚本文件
在 App 中加载 JS 代码的方式有两种,一种是写到代码中,适合调试和测试;另一种是通过远程 URL 动态加载,适合在产品发布后进行紧急修补。
在本地写 js 代码:
```objective-c
- (void)viewDidLoad {
[super viewDidLoad];
NSDate *date = [NSDate date];
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"bg.jpg"]];
self.dateLabel = ({
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 200, 40)];
label.textColor = [UIColor whiteColor];
label.font = [UIFont systemFontOfSize:20];
label.text = [NSString stringWithFormat:@"%@", date];
label;
});
[self.view addSubview:self.dateLabel];
}
//手动生成 main.js 文件
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
if (script) {
[JSPatch evaluateScript:script];
}
```
在远程 URL 导入 JS 脚本:
```objective-c
#if 0 //禁用 Online Config 功能
[JSPatch setupLogger:^(NSString *message) {
NSLog(@"%@", message);
}];
[JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
if (type == JPCallbackTypeUpdate) {
NSLog(@"JSPatch: %@ has new content.", [data objectForKey:@"identifier"]);
} else if (type == JPCallbackTypeRunScript) {
NSLog(@"JSPatch: Run script: %@.", data);
} else if (type == JPCallbackTypeCondition) {
NSLog(@"JSPatch: Condition: %@, %@, %@.", data[@"condition"], data[@"satisfy"], data[@"identifier"]);
} else if (type == JPCallbackTypeGray) {
NSLog(@"JSPatch: Gray: %@, %@.", data[@"isGray"], [data objectForKey:@"identifier"]);
}
}];
[JSPatch testScriptInBundle];//测试是否成功
[JSPatch showDebugView];//对应的视图,用于调试
[JSPatch sync];
#else
[JSPatch setupLogger:^(NSString *message) {
//关闭 Online Config 功能后日志依旧可用
//NSLog(@"message: %@", message);
}];
//获取低权限 js 列表(just permission:read)
//[JSPatch requestJDPermissionURL];//京东相关
//[JSPatch requestTencentPermissionURL];//QQ相关
[JSPatch requestPermissionURL];
#endif
```
## 二、JSPatch 脚本编写
JSPatch 的代码必须使用 JavaScript 语言编写,而且必须遵循 JSPatch 框架规则。这里主要介绍 JSPatch 中包含的若干基本语法和规则:
### 2.1、定义变量和类型
JSPatch 的变量和类型定义可以和 Objective-C 相同,如下所示:
```javascript
//定义变量
var count = 1;
var greeting = "Hello World";
//定义数组
var phrases = ["Hello", "World"];
//定义字典
var dictionary = {"Name": "Joe", "Age": 20};
```
### 2.2、代码结构
下面举例说明 JSPatch 的代码结构:
```javascript
require('UIView, UIColor');
defineClass('UIViewController', {
viewDidLoad: function() {
self.super().viewDidLoad();
self.view().setBackgroundColor(UIColor.grayColor());
}
}, {});
defineClass('MyView', {
fun: function() {
var a = 3;
var b = 4;
var result = a + b;
console.log("result:" + result);
}
}, {});
```
这是一个 JSPatch 文件的基本结构,分为三部分:`require`、`defineClass`和参数对象 `{}`。其中,`require`是必需的,其他两个部分是可选的。
### 2.3、classList和registerClass
- classList:用于获取当前所有的 class。
- registerClass:用于注册类,注册后,才能从类使用到其 instanceMethod , classMethod
示例:
```javascript
require('UIView,UIColor,UIButton,UILabel')
var btn = UIButton.buttonWithType(0);//UIButtonTypeCustom=0,UIButtonTypeSystem =1;
btn.setTitle_forState('我是按钮', 0);//0 默认状态,1 高亮状态,2 选中状态
btn.setTitleColor_forState(UIColor.blackColor(),0);
btn.addTarget_action_forControlEvents(self,'buttonTap',1 << 6);
btn.sizeToFit();
btn.setCenter(self.view().center());
self.view().addSubview(btn);
var label = UILabel.alloc().initWithFrame(CGRectMake(0, 0, 200, 50));
label.setCenter(CGPointMake(self.view().center().x, btn.center().y + 100));
label.setTextAlignment(1);
label.setText("有一个Label");
self.view().addSubview(label);
/**
通过 require('UIKit') 导入了 UIKit ,可以通过UI 规定了的类名来获取指定的类。
下面我们写一个例子来演示获取当前所有的类名并遍历打印它们的类名,与之对应的函数是classList()。
*/
var allClass = require('UIApplication').classList()
for (var i = 0; i < allClass.length; i++) {
console.log("Class name: " + allClass[i]);
}
registerClass({
name: 'MyCustomView:UIView',
protocols: ['UITextFieldDelegate'],
methods: {
jc_bugMethod_newMethodToObjC: function() {
console.log('类似用 AS 方法和 obj-c 联系', '');
}
}
});
defineClass("MyCustomView", {
init: function() {
self = self.super().init()
self.setBackgroundColor(UIColor.yellowColor())
return self
},
performAsyncTask: function() {
var _this = this;
self.addLoadingView()
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), function() {
self.removeLoadingView()
_this.showInfoView()
})
},
addLoadingView: function() {
var loadingView = UIView.alloc().initWithFrame({x: 0, y: 0, width: 100, height: 100})
loadingView.layer().setCornerRadius(50)
loadingView.setCenter(self.center())
loadingView.layer().setMasksToBounds(1)
loadingView.setBackgroundColor(UIColor.lightGrayColor())
self.addSubview(loadingView)
},
removeLoadingView: function() {
self.subviews().lastObject().removeFromSuperview()
},
showInfoView: function() {
JCAlertView.new().showWithTitle_message_cancelButtonTitle_otherButtonTitles(0, "更多讯息:", "取消", ["确认", "拒绝", "分裂", "爆炸", "液化"])//UIAlertController
}
});
```
### 2.4、使用方法和调用 OC 方法访问属性
- 方法、属性的调用需要用 '()' 和 '.';
- 参数类型转换时可使用框架中提供的方法。
```javascript
var objJosn = {
"name": "Tom",
"age": 18
};
objJosn.hello = function (){
console.log("我是后加属性 Hello");
}
console.log("objJosn:" + JSON.stringify(objJosn));
require('UIColor')
defineClass('ViewController', {
viewDidLoad: function() {
self.super().viewDidLoad();
self.view().setBackgroundColor(UIColor.grayColor());
self.testString();
},
testString: function() {
console.log("enter testString of js");
var array = ["hello", "world"];
var str = array.join(" ");
console.log(str);
var tempstr = str.substring(2, 5);
console.log("tempstr:" + tempstr);
var str = "hello world";
var changeStr = str.replace("hello", "goodbye");
console.log("changeStr:" + changeStr);
var dataStr = "2017-10-20 10-20-30";
var date = new Date(dataStr);
console.log("date:" + date);
console.log("js have property of objc_countOfObj:" + self.objc_countOfObj(34));
},
objc_createView: function() {
var f = CGRectMake(0, 0, 200, 100);
var myView = UIView.alloc().initWithFrame(f);
myView.setBackgroundColor(UIColor.redColor());
return myView;
},
objc_returnFloat: function() {
return 3.14159;
},
objc_countOfObj: function(obj) {
return obj.count();
}
}, {});
```
### 2.5、方法交换
使用 JSPatch,我们可以直接交换 ObjC 方法,而不需要重新编译应用。
下面是交换 UIButton 的setTitle:forState:方法的代码。在这里,我们生成一个新的方法 replace_setTitle:forState:,将其和 NSObject 原来的方法 setTitleColor:forState: 进行交换:
```javascript
require('UIButton');
defineClass('UIButton', {
replace_setTitle: function(title, state) {
self.replace_setTitle(title + " :j1024->JSPatch", state);
},
});
```
## 三、热更新案例
下面我们来讲解一下使用 JSPatch 实现热更新的案例。
案例需求:我们已经将项目提供了一份 v1.0 版本的 iOS App,但是出现一个新的需求:需要在首页推荐中增加一个展示 Dashboard 的按钮。可是,这个需求我们发现遗漏了的,没有加入产品规划和 UI 设计中,所以并不会在紧急发布版本中加入。
但是,在使用 JSPatch 技术的情况下,我们只需要新增一个 js 脚本,就可以快速帮助我们添加这个按钮。具体实现如下:
1. 编写 JS 代码。
在本地创建一个 main.js 文件,写入下列代码,即可实现在首页增加一个推荐按钮的功能。
```javascript
require('UIButton,UIColor');
defineClass('ViewController', {
viewDidLoad: function() {
self.super().viewDidLoad();
self.view().setBackgroundColor(UIColor.grayColor());
self.navigationItem().setTitle("j1024 -> JSPatch");
var recommendBtn = UIButton.buttonWithType(0);
recommendBtn.setFrame({x: 10, y: 100, width: 100, height: 40});
recommendBtn.setBackgroundColor(UIColor.blueColor());
recommendBtn.setTitle_forState("推荐", 0);
recommendBtn.addTarget_action_forControlEvents(self, "onClickRecommendBtn", 64);
self.view().addSubview(recommendBtn);
},
onClickRecommendBtn: function() {
console.log("点击了推荐按钮");
},
})
```
2. 在 AppDelegate.m 的 didFinishLaunchingWithOptions 方法中添加如下代码。
```objective-c
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
if (script) {
[JSPatch evaluateScript:script];
}
```
3. 打开 App ,就可以看到新增加的推荐按钮了。
如此,我们已经实现了在项目紧急情况下,添加变更需求点的 JSPatch 实现方式。
## 四、总结
JSPatch 能够大大方便 iOS 开发者修改代码和实现热更新。但是,在使用 JSPatch 的过程中需要注意以下几个问题:
1. JS 编写遵循 JSPatch 框架规则。
2. JSPatch 在后退版本时,相当于会把当前更改过的业务代码删除,所以业务代码需备份。
3. 少规模部署,重复部署会导致同名的类叠加。
使用 JSPatch 实现热更新有很多需要考虑的方面,比如需要做好风险管控和版本管理等。在此,我们只是介绍了 JSPatch 的基本使用和代码调试的一部分内容,希望能够帮助 iOS 开发者使用 JSPatch 修补和调试自己的 iOS App 代码。 如果你喜欢我们三七知识分享网站的文章, 欢迎您分享或收藏知识分享网站文章 欢迎您到我们的网站逛逛喔!https://www.37seo.cn/
发表评论 取消回复