JSPatch 是什么
JSPatch 是一个开源项目(Github链接),只需要在项目里引入极小的引擎文件,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,替换任意 Objective-C 原生方法。目前主要用于下发 JS 脚本替换原生 Objective-C 代码,实时修复线上 bug。
JSPatch平台又是什么鬼
JSPatch需要你自己搞一个服务器管理、下发脚本,还要处理安全问题,高并发问题,烦死你。JSPatch平台封装了SDK,你只需要继承SDK就可以省去一堆的麻烦。
JSPatch 需要使用者有一个后台可以下发和管理脚本,并且需要处理传输安全等部署工作,JSPatch 平台帮你做了这些事,提供了脚本后台托管,版本管理,保证传输安全等功能,让你无需搭建一个后台,无需关心部署操作,只需引入一个 SDK 即可立即使用 JSPatch。
JSPatch 平台的 SDK 在核心代码的基础上增加了向平台请求脚本/传输解密/版本管理等功能,只用于这个平台。
通过 JSPatch 平台上传的脚本文件都会保存在七牛云存储上,客户端 APP 只跟七牛服务器通讯,支持高并发,CDN分布全国,速度和稳定性有保证。
SDK接入
这种问题不要问我,注册一个账号,建一个app获取到appid,然后接入SDK完事儿。至于你是用cocoapods还是手动接入,全凭个人喜好。SDK接入
API主要方法
+startWithAppKey:
传入在平台申请的 appKey,启动 JSPatch SDK。在-application:didFinishLaunchingWithOptions:
开头初始化第三方库的时候一并调用初始化。
+sync
与 JSPatch 平台后台同步,询问是否有 patch 更新,如果有更新会自动下载并执行。
每调用一次 +sync 就会请求一次后台,如果app启动的时候检查一次就OK的话就在
-application:didFinishLaunchingWithOptions:
调用一次。如果实时性要求高,就在
-applicationDidBecomeActive:
的时候调用。
+setupLogger:
SDK打一些请求和执行的log,默认是NSLog()
输出,如果app有自己的日志系统,并且希望自己的日志系统拿到这些log,则在+startWithAppKey之前调用这个方法。
+testScriptInBundle
写好的脚本上线前总要测试一下吧,就是用这个方法。需要把main.js文件拖到项目中,并且不要调用+startWithAppKey:方法。
注意!!!:测试完成一定要删除main.js,血淋淋的教训是,如果不删除,线上的脚本down下来之后,无法确定会执行哪个main.js,莫名其妙的问题,并且很难找到。切记切记
+setupCallback:
JSPatch 执行过程中的事件回调,在以下事件发生时会调用传入的 block:
typedef NS_ENUM(NSInteger, JPCallbackType){
JPCallbackTypeUnknow = 0,
JPCallbackTypeRunScript = 1, //执行脚本
JPCallbackTypeUpdate = 2, //脚本有更新
JPCallbackTypeUpdateDone = 3, //已拉取新脚本
JPCallbackTypeCondition = 4, //条件下发
JPCallbackTypeGray = 5, //灰度下发
};
例如
[JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
switch (type) {
case JPCallbackTypeUpdate: {
NSLog(@"updated %@ %@", data, error);
break;
}
case JPCallbackTypeRunScript: {
NSLog(@"run script %@ %@", data, error);
break;
}
default:
break;
}
}];
+setupUserData:
定义用户属性,在+sync:
之前调用,用于条件下发,可以用来做AB测试。什么是AB测试?自己去Google啊...
[JSPatch setupUserData:@{
@"userId": user.userId,
@"location": user.location,
@"gender":user.gender,
@"age":user.age
}];
发布补丁的时候选择条件下发,写入相应的条件就可以实现条件下发。例如图中性别是女,年龄小于35岁的用户显示特定的内容。还可以选择手机系统的版本。
+setupDevelopment
开发者预览模式,可以在 debug 模式下测试补丁效果。
[JSPatch startAppWithKey:@""];
#ifdef DEBUG
[JSPatch setupDevelopment];
#endif
[JSPatch sync];
灰度下发
这个功能太实用了,选择灰度下发可以按照比例灰度和人数灰度下发。比例灰度例如随机挑选 30% 的设备生效;人数灰度比如只安装1000台设备。应用场景:
- 先下发一批看看效果,如果OK就全量下发
- 只对部分用户下发,显示特定的效果(和条件下发类似)
实战
扯了这么多终于到实战了。
背景:
- 项目已经集成了SDK
- 注册过了平台账号
- 已经注册了APP获得了appkey
- 已经上线了集成过JSPatch SDK的app
- 这个上线的版本出现了大量的crash,crash率很高,不马上解决,老板就会马上解决你...
线上的代码是这样的,数组访问越界了
@implementation DSHomeViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *content = self.dataSource[[indexPath row]]; //可能会超出数组范围导致crash
DSGoodsViewController *controller = [[DSGoodsViewController alloc] initWithContent:content];
[self.navigationController pushViewController:controller];
}
@end
修改源代码
修改后的代码
@implementation DSHomeViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(self.dataSource.length > indexPath.row){
NSString *content = self.dataSource[indexPath.row];
DSGoodsViewController *controller = [[DSGoodsViewController alloc] initWithContent:content];
[self.navigationController pushViewController:controller];
}
}
@end
编写补丁脚本
打开JSPatch代码转换器,原生代码转化为JS代码
转化成功了,但是要数组,需要修改一下。常见问题 修改之后的脚本,为了能够知道脚本运行,第一行加上log
console.log('JSPatch Run Success');
require("DSGoodsViewController");
defineClass("DSHomeViewController", {
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var row = indexPath.row();
if (self.dataSource().length() > row) {
var content = self.dataSource()[row];
var controller = DSGoodsViewController.alloc().initWithContent(content);
self.navigationController().pushViewController(controller);
}
}
}, {});
测试脚本
写好之后的脚本存为main.js放到项目中,在-application:didFinishLaunchingWithOptions:
方法中打开测试
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//[JSPatch startWithAppKey:myAPPKey];
//[JSPatch sync];
[JSPatch testScriptInBundle];
….
}
编译运行会看到控制台log输出JSPatch Run Success
然后......怎么能少了调试呢
在Safari中断点调试
开启 Safari 调试菜单
Safari -> 偏好设置 -> 高级 -> 勾选[在菜单栏中显示“开发”菜单]
启动app进行调试
启动APP -> Safari -> 开发 -> 选择你的机器 -> JSContext
在 iOS8 下,JSPatch 支持使用 Safari 自带的调试工具对 JS 脚本进行断点调试,界面大致长这样
上传脚本
- 在平台上新建一个线上的版本
- 把调试通过的脚本main.js上传到这个线上的版本
- 选择全量下发(因为要搞定crash)
- 删除本地的main.js
- 好了,等着下发之后crash率降下来,饭碗保住了
常见的问题
- 不能用
NSLog('xx')
,应该用console.log('xx')
- get property 记得加括号,例如
self.navigationItem()
,而不是self.navigationItem
- 私有成员变量要用
self.valueForKey()
和self.setValue_forKey()
接口存取 - block 里不能直接使用 self,应该在block外定义var myself = self;
未完待续。。。