iOS JSPatch 热更新(HotFix)线上bug

苹果大大今天发出了警告邮件,貌似禁掉了热更新;具体关注 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文件加密处理。


安全性加密.png

4、其他:我们可以使用JSPatch热修复bug,甚至可以开发新的项目。JSPatch也可以多个版本留存兼容。注意:热修复我认为是非常规手法,所以我们的bug在下一个版本发布前必须用OC代码修复,不可在走热修复。 具体请看JSPatch作者文档。下面把相关链接贴出来:

在此感谢作者开源 github:JSPatchJSPatch-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。


流程图.png

下面来说下具体怎么集成到项目中

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到工程中就可,借助它的平台,进行补丁的上传工作,省去了自己配置服务器等。**但是 但是** 这么好的东西,作者也不容易啊,所以这种方法是要付费的,没错**付费的**,自己配置就不用了,具体怎么搞,你就自己选择吧。。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容