苹果大大今天发出了警告邮件,貌似禁掉了热更新;具体关注 JSPatch issues 和 Facebook RN框架回复
或是我的这个文章:苹果爸爸放大招,禁掉JSPatch热更新(Apple爹等已给回复)
首先以我的理解介绍下JSPatch的应用场景、工作原理和使用方法:
1、应用场景:当我们已经发布上线了项目,在使用过程中发现了bug,紧急发包来不及,或是不值得发一次包的时候,可以使用JSPatch来热修复这个bug。
2、工作原理:首先将项目中的bug修复好,再将这个方法(也有可能是一个类、甚至新建一个类)转换成对应的JS代码格式【ps:为什么使用js代码呢,因为服务器PHP不能识别我们的oc代码】,交给后台上传到服务器。等到下次启动APP的时候,我们就要获取到这个JS内容(你可以选择下载下来整个JS文件,或是只取得我们想要的JS内容),当我们在运行到之前那个bug的时候,就会将这个方法拦截,执行JS新的方法,即替换掉了有bug的这个方法,从而完成了热修复。
3、使用方法:第一次部署的时候,和后台服务器约定好,怎么获取到这个JS文件,一般新开一个接口,专门获取这个JS文件以及判断时候需要更新(设置一个值,有变化时就更新)。为了防止别人轻易截取我们的方法,我们要对JS文件加密处理。
4、其他:我们可以使用JSPatch热修复bug,甚至可以开发新的项目。JSPatch也可以多个版本留存兼容。注意:热修复我认为是非常规手法,所以我们的bug在下一个版本发布前必须用OC代码修复,不可在走热修复。 具体请看JSPatch作者文档。下面把相关链接贴出来:
在此感谢作者开源 github:JSPatch、 JSPatch-Wiki 、和在线转换工具 以及OC代码转JS文件的基本规则:基础用法
现在已经有了官方网站,直接集成sdk,更方便了。JSPatch 官网
常见的转换注意事项及转换格式:
**基础原理**
JSPatch 能做到通过 JS 调用和改写 OC 方法最根本的原因是 Objective-C 是动态语言,
OC 上所有方法的调用/类的生成都通过 Objective-C 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);
注意点:
1,关于打印:
不能使用NSLog函数。
console.log("success");
var tmpAAAA = self.redirectUrlStr();
console.log(tmpAAAA);
2,isKindOfClass,
if (viewController.isKindOfClass(LoginVC.class())) {}
3,OC的常量,需要用他具体的值来代替。TURE/YES(1),FALSE/NO(0)也是。
4、引用的宏要写对应具体的数值代替。
eg:UIControlStateNormal ----> 0, UIControlStateHighlighted ----> 1 << 0,
5,空串判断
if (_redirectUrlStr) // 无法识别,需要用一下方法来判断
if (self.redirectUrlStr().length() > 0)
6,需要修改多个方法:[多个方法用法](http://www.jianshu.com/p/a8e1f3f07f5c)
defineClass('SampleClassA', {});
defineClass('SampleClassB', {});
7,CGRect取值:rect.x,rect.y,rect.width,rect.heigh --》 [].width,[].top,[].left
UIEdg:({top:0, left:image.size().width*0.5, bottom:0, right:0})
({x:0,y:0,width: UIScreen.mainScreen().bounds().width, height:UIScreen.mainScreen().bounds().height})
8,拼接字符串:替换stringformatqer方法。
var text = tmpProduct.merchandiseDiscount().toFixed(1)+'折';
var marketPrice = '¥'+ tmpProduct.marketPrice();
(有时候,直接这样拼接,会显示Object[Object]),这貌似根JS的字符段对象有关系。
ps:toFixed(n) ---> 保留几位小数
9,获取数据个数:tmpProduct.titleTags().count()
10,获取数组对应下标元素:colorfulTags.objectAtIndex(0)
获取字典的值:要用objectForKey; eg:var cellType = cellInfo.objectForKey("cellType");
11,for in循环,改成for循环。
for (var i = 0; i < colorfulTags.count(); i++){
var lbl = colorfulTags.objectAtIndex(i);
lbl.setHidden(1);
}
12,__weak 和 __strong
var weakSelf = __weak(self)
block里的self 要转换成这样:var tmpSelf = self;
13,代理方法
if (self.dataDelegate() && self.dataDelegate().respondsToSelector("webViewDidFailLoadWithError:"))
{
self.dataDelegate().webViewDidFailLoadWithError(error);
}
14,原OC方法里的,带下划线的属性,或者是调用的带下划线的方法名,需要用两个下划线代替。
Product.goods_maidian() —> Product.goods__maidian()
sd_setImageWithURL —> sd__setImageWithURL
15,带下划线的私有变量。
获取:var tmpProduct = self.valueForKey("_product");
获取:var productName = self.valueForKey("_product_name");(获取时,直接将下划线删掉)
赋值:self.setValue_forKey(product, "_product");
16,方法里用的所有私有变量,都要先获取,才可以使用,直接用私有变量是错误的。
var maiDianLabel = self.valueForKey("_maiDianLabel") ;(获取买点的Label私有变量)
17,拦截的方法里,只要出现类名的,都需要放到require里,逗号隔开。系统自动可能转义不全,自己添加。
require('NSString,UIImage,NSURL,UIColor,WZHProduct, MASConstraintMaker,UIScreen');
18,一些固定的变量,例如字体样式的key,先通过代码,NSLog打印出具体的值,然后再替换JS代码。
OC:@{NSFontAttributeName :_promoteIco.font}
JS:{NSFont: promoteIco.font()}
19,拦截系统的代理方法的时候。
必须要测试。像WebView的封装类,拦截了WebView的代理方法
(估计是UIWebView也是用了OC的JS库,冲突导致)
20,block**限制**
从 JS 传 block 到 OC,有两个限制:
A. block 参数个数最多支持6个。(若需要支持更多,可以修改源码)
B. block 参数类型不能是 double / NSBlock / struct 类型。
附上一段转义的代码
OC代码:
类名:WebActivityVC.h
-(void)managerPushViewController:(UIViewController *)viewController{
if ([viewController isKindOfClass:[LoginVC class]]) {
BaseNavigationController *navigate = [[BaseNavigationController alloc] initWithRootViewController:viewController];
[self.navigationController presentViewController:navigate animated:YES completion:nil];
return;
}
[self.navigationController pushViewController:viewController animated:YES];
}```
对应的JS代码:
require('LoginVC,BaseNavigationController');
defineClass('WebActivityVC', {
managerPushViewController: function(viewController) {
if (viewController.isKindOfClass(LoginVC.class())) {
var navigate = BaseNavigationController.alloc().initWithRootViewController(viewController);
self.navigationController().presentViewController_animated_completion(navigate, YES, null);
return;
}
self.navigationController().pushViewController_animated(viewController, YES);
},
});```
客户端具体策略:
1.用户打开App时,同步进行本地补丁的加载。
2.用户打开App时,后台进程发起异步网络请求,获取服务器中当前App版本所对应的最新补丁版本和必须的补丁版本。
3.获取补丁版本的请求回来后,跟本地的补丁版本进行对比。
4.如果本地补丁版本小于必须版本,则提示用户,展示下载补丁界面,进行进程同步的补丁下载。下载完成后重新加载App和最新补丁,再进入App。
5.如果本地补丁版本不小于必须版本,但小于最新版本,则进入App,不影响用户操作。同时进行后台进程异步静默下载,下载后补丁保存在本地。下次App启动时再加载最新补丁。
6.如果版本为最新,则进入App。
下面来说下具体怎么集成到项目中
1、你可以先搞一个本地测试文件,方便需要使用的时候调试。直接本地存储就好了。
2、大体步骤就是:先和服务器约定好了,让我可以取到这个需要的JS文件。
3、APP中部署:
经验建议
1、重要的地方,比如购物流程、涉及到支付、生成订单、选择支付方式等事件动作的地方进行JSPatch埋点。以防一旦出现bug,只需要修改这个埋点事件,不用整个方法都修改。
2、尽量能简单调用方法,就不要把很多处理放在同一个方法中。防止一旦需要热修复,要修改很多不必要的逻辑方法。费时间。。
3、声明属性、变量的时候,不要写下划线。这是致命的。。一般人也不会这么写吧:@property (nonatomic, strong) UIButton *_loginOutBtn;
给我把这个_loginOutBtn前面下划线去掉。去掉。。
小技巧(很有用哦)
如果你的方法代码非常多,这个时候整个方法代码进行代码转换就相当耗时间。如果你要修改的代码刚好在方法的最后面(哪个位置都适用啦),这个时候我们可以把方法前面的代码用下面代码替代
//在我们需要替换的方法名前面加上ORIGupdateContent方法
self.ORIGupdateContent();
//在这行代码后面加上你需要修改的代码,上面一句代码相当于把整个方法已经执行了一遍,所以我们不需要把整个方法的代码进行替换```
例如:下面这个方法本来是有200行代码,但是我刚好要替换的代码是在最后面,所以我在方法前面加上self.ORIGupdateContent();就相当于是把方法已经执行了一遍,就不需要把所有的代码都进行转换。只需要修改你要修改的那里就好了。
![例子.png](http://upload-images.jianshu.io/upload_images/2286585-806c383049050604.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
PS:
①.接入了JSPatch之后,iOS的线上BUG 看上去就不向以前那样“猛如虎”了,但是这仅仅是一个紧急预案措施,以前规范的流程还是需要遵守。
②.每一次本版本用JSPatch解决的线上Bug,下个版本必须用OC代码写入项目中,不能允许补丁代码的存留超过一个版本。
③.倡导使用敏捷开发的思想,类似于主逻辑或者是功能模块入口的方法可以抽的更细,这样即使需要修改,成本也不会太大,作者本人也提到,如果有一行代码必须要在一个大方法的中间进行修改,那我也没办法了,你只能把这整个方法都用js写一遍了,所以才设置了JSPatchConvertor。
④.每次用JSPatch解决掉的线上BUG 应当有一个专门的文档记录,遇到重复错误必须写casestudy。
####原谅我在最下面告诉你们最新的方法 毕竟我写了这么多不容易啊
JSPatch 有现成的SDK,直接拖SDK到工程中就可,借助它的平台,进行补丁的上传工作,省去了自己配置服务器等。**但是 但是** 这么好的东西,作者也不容易啊,所以这种方法是要付费的,没错**付费的**,自己配置就不用了,具体怎么搞,你就自己选择吧。。