浅析JSPatch的使用

浅析JSPatch的使用

一. 背景介绍

  • 背景:iOS作为苹果独家开发和运营的生态圈,具有非常封闭的运作环境,其App上线需要通过提交,排队,审核,上线这四个很长的过程,过往的排队时间长至于7~15天,对一个软件迭代就有了极大的限制,如果产品中出现了小bug, 需要修复问题的话就是及其困难的了,虽然目前苹果的审核时间已经短至1天,但是其App更新方式为全量更新,如若为了修复一个极小的问题而强迫用户更新一个版本的话,体验也是不好的, 所以在多年的技术进化中,产生了App热更新这样的一个需求。

  • 发展: 在这几年来的iOS开发界,各个厂商和各种极客各现神通,通过各种黑科技来实现各种热更新技术的实现,在iOS7之前,著名的有Wax框架,Wax框架主要使用Lua语言进行脚本实现,并且需要在原生的App中植入Wax的脚本引擎,使用上也不是太方便,所以逐渐被淘汰。2013年,苹果在iOS7中引入了原生框架JavascriptCore这样一个原生框架,彻底打开了JS脚本和native调用之间的桥梁,也为热更新技术的实现提供了原生的技术支持,各种极客和软件开发商都定制自己的脚本来调用本地的部分代码,但是都没有统一的方案,在这一个沉淀的过程中,产生了较为成熟易用的JSPatch框架。

二. JSPatch介绍

  • 诞生:JSPatch诞生于2015年5月,最初出自于腾讯广研院的高级iOS开发工程师@bang的个人项目,其超级深厚的开发基础和过人的天赋在这个项目中表现的淋漓尽致,其基本原理为使用javascriptcore中提供的调用Objective-C的原生入口,并充分利用Objective-C的动态特点来在运行时修改方法的入口和实现,用于替换原有的旧方法。 目前JSPatch在Github上已经开源,拥有大量的拥簇,并且充分在腾讯,阿里,百度各个大厂的产品中得到了验证,腾讯甚至提供了JSPatch托管SDK平台用于商用,可见其实现已经成熟到了商用的地步,可以放心使用了。以下为其他开发者总结的JSPatch和Wax的区别:
JSPatch_Wax.jpg

基本原理

  • Objective-C是动态语言,具备运行时特性,所以能够在运行时通过类名称或者方法的名称来获取执行入口,并且进行实际调用,而且还可以通过runtime特性来swizzle各种方法,进行动态修改。
  • 通过类和方法名称进行运行时调用的片段:
Class class = NSClassFromString(@"UIViewController");        
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString(@"viewDidLoad");
[viewController performSelector:selector];

动态替换类方法为新方法的片段:

Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);

正是由于OC语言具有如上片段展示的runtime时决定调用方法的特性,成就了热更新的基础原理。

具体的实现原理比较复杂,作者已经开源了代码,并且在博客中写明了实现过程中的各种问题,如果有兴趣的可以参阅以下链接:
JSPatch在github的源地址,
JSPatch详细实现原理

三. 使用方法

  • 基本语法解析

下面展示一下OC代码和热更新的JS脚本的对应代码片段,例如线上 APP 有一段代码出现 bug 导致 crash:

@implementation JPTableViewController
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  NSString *content = self.dataSource[[indexPath row]];  //可能会超出数组范围导致crash
  JPViewController *ctrl = [[JPViewController alloc] initWithContent:content];
  [self.navigationController pushViewController:ctrl];
}
...
@end

可以通过下发这样一段 JS 代码,覆盖掉原方法,修复这个 bug:

//JS
defineClass("JPTableViewController", {
  //instance method definitions
  tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
    var row = indexPath.row()
    if (self.dataSource().length > row) {  //加上判断越界的逻辑
      var content = self.dataArr()[row];
      var ctrl = JPViewController.alloc().initWithContent(content);
      self.navigationController().pushViewController(ctrl);
    }
  }
}, {})

除了修复一些bug之外,也可以动态的修改一些App的行为, 详细越发可以查阅
JSPatch语法WIKI

  • SDK接入使用

前面已经提到,腾讯已经将JSPatch进行了商用包装,使用方式非常简单,进入JSPatch的官方站点,下载SDK,将SDK拖入工程,然后添加官方javascriptcore.framework就可以按照文档进行使用了,腾讯官方还进行了后端平台托管,使用者在后端注册App获取Key,然后在App端初始化即可使用,需要热更新的脚本也配置在后端进行下发,使用非常方便简洁,该平台提供每天1W次下发的免费量,超过1W次需要收费服务,详细使用方式可以查阅:
JSPatch官方网站以及介绍

  • 源代码引入使用

首先构建一个简单的demo, demo洁面只有一个按钮,按钮下面一个Label框,demo的简单逻辑是,点击按钮之后,Label框背景颜色变为蓝色,并且显示native code字样。

原始代码片段如下:

demoSource.png

初始运行界面为:

demoOne.png

点击changeColor按钮之后,事件响应并执行,运行界面变为:

demoBlue.png

以上为native代码的原始逻辑,下面我们开始进行动态替换:

  • 思路

我们要动态替换changeColor的点击事件,其实就是需要动态替换clickChangeColorEvent这个函数,大家可以去按照上面的JSPatch的语法wiki中去查阅详细语法,我们这里有更简单直接的方法,JSPatch的作者为了使用者方便,开发了
JSPatchConverter这个工具,大家可以去详细看一下使用,也可以直接下载成品App, 我们这里使用成品App进行JS代码生成,只需要在App的左侧写入要替换的方法,点击convert按钮,右边窗口即可生成热更新的脚本代码,如图:

jsconverter.png

将右边窗口的成品JS片段自己保留出来,然后放在自建的服务器准备下发使用。

  • 本地工程准备

从github中下载JSPatch的源码,并在源码工程中找到JSPatch文件夹,将该文件夹拖入demo工程,如图:

treeOne.png

同时添加苹果官方的javascriptcore.framework,如图:

treeTwo.png
  • 代码实现

首先要在AppDelegate.h中添加对应的头文件:

#import "JSPatch/JPEngine.h"

继而在iOS的App启动入口函数中添加JSEngine的初始化:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    [JPEngine startEngine];
    
    return YES;
}

以上两步已经完成了JSEngine的简单的初始化过程,然后进入下一步,获取远端脚本的过程。 将前面在JSConverter中生成的脚本代码自己放入自建服务器中(这一步自己寻找后端兄弟协助完成), 然后在实现一个获取脚本的函数,进行脚本获取,并将执行获取脚本的函数放在[JPEngine startEngine]之后执行,然后在获取到脚本之后,执行脚本行为,以下为完整范例:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    [JPEngine startEngine];
    [self getAndRunJSPatchScript];
    return YES;
}

- (void)getAndRunJSPatchScript
{
    NSURLSession *urlSession = [NSURLSession sharedSession];
    NSMutableURLRequest *httpRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxxxx/api"]];
    [httpRequest setHTTPMethod:@"POST"];
    NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:httpRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if(!error)
        {
            NSString *targetJSPatchScript = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            [JPEngine evaluateScript:targetJSPatchScript];
        }
    }];
    [dataTask resume];
}

然后运行新的程序,点击changeColor按钮,行为已经发生了改变:

demoYellow.png

可以看到,图中的色块已经变成了黄色,图中的提示语言也显示JSPatch代码进行了执行,而这一个过程当中,native的逻辑代码并没有改变。

  • 归纳

上面代码片段中的getAndRunJSPatchScript函数只是简单的调用了接口,获取了脚本,然后执行。更长远的规划,服务端完全可以实现一个脚本管理服务,提供一系列结构,将不同App版本,不同运营行为的脚本进行分类管理,App只需要上报自己的App基本信息,然后由服务端来返回要热更新的脚本,更加的灵活和体系化。

总结

上述内容可以看到JSPatch确实是一个很方便的热更新库,但是个人认为在App的开发过程中,大家还是应该把质量管控在开发阶段,不可依赖于这样的热更新修复,这样的修复可以用于应急来修补漏网的bug, 但是不建议作为App的主要逻辑开发层,因为这样的会带来更多的外部管理App的功能,从某种意义上来说也添加了运营的复杂性, 而且在苹果主推的未来开发语言swift中,这种OC语言所具有的动态性将会不再存在,以这套原理来运作的JSPatch库也就无从使用了,但是在当下的实际OC仍然是很多App的主开发语言的时代,这个热更新修复的库还是非常非常实用的,至于各位开发者将会对这个库依赖多深,这就是自己定夺的问题了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容