淺析JSPatch的使用

淺析JSPatch的使用

一. 背景介紹

  • 背景:iOS作為蘋果獨家開發(fā)和運營的生態(tài)圈,具有非常封閉的運作環(huán)境,其App上線需要通過提交,排隊,審核,上線這四個很長的過程,過往的排隊時間長至于7~15天,對一個軟件迭代就有了極大的限制,如果產(chǎn)品中出現(xiàn)了小bug, 需要修復(fù)問題的話就是及其困難的了,雖然目前蘋果的審核時間已經(jīng)短至1天,但是其App更新方式為全量更新,如若為了修復(fù)一個極小的問題而強迫用戶更新一個版本的話,體驗也是不好的, 所以在多年的技術(shù)進化中,產(chǎn)生了App熱更新這樣的一個需求。

  • 發(fā)展: 在這幾年來的iOS開發(fā)界,各個廠商和各種極客各現(xiàn)神通,通過各種黑科技來實現(xiàn)各種熱更新技術(shù)的實現(xiàn),在iOS7之前,著名的有Wax框架,Wax框架主要使用Lua語言進行腳本實現(xiàn),并且需要在原生的App中植入Wax的腳本引擎,使用上也不是太方便,所以逐漸被淘汰。2013年,蘋果在iOS7中引入了原生框架JavascriptCore這樣一個原生框架,徹底打開了JS腳本和native調(diào)用之間的橋梁,也為熱更新技術(shù)的實現(xiàn)提供了原生的技術(shù)支持,各種極客和軟件開發(fā)商都定制自己的腳本來調(diào)用本地的部分代碼,但是都沒有統(tǒng)一的方案,在這一個沉淀的過程中,產(chǎn)生了較為成熟易用的JSPatch框架。

二. JSPatch介紹

  • 誕生:JSPatch誕生于2015年5月,最初出自于騰訊廣研院的高級iOS開發(fā)工程師@bang的個人項目,其超級深厚的開發(fā)基礎(chǔ)和過人的天賦在這個項目中表現(xiàn)的淋漓盡致,其基本原理為使用javascriptcore中提供的調(diào)用Objective-C的原生入口,并充分利用Objective-C的動態(tài)特點來在運行時修改方法的入口和實現(xiàn),用于替換原有的舊方法。 目前JSPatch在Github上已經(jīng)開源,擁有大量的擁簇,并且充分在騰訊,阿里,百度各個大廠的產(chǎn)品中得到了驗證,騰訊甚至提供了JSPatch托管SDK平臺用于商用,可見其實現(xiàn)已經(jīng)成熟到了商用的地步,可以放心使用了。以下為其他開發(fā)者總結(jié)的JSPatch和Wax的區(qū)別:
JSPatch_Wax.jpg

基本原理

  • Objective-C是動態(tài)語言,具備運行時特性,所以能夠在運行時通過類名稱或者方法的名稱來獲取執(zhí)行入口,并且進行實際調(diào)用,而且還可以通過runtime特性來swizzle各種方法,進行動態(tài)修改。
  • 通過類和方法名稱進行運行時調(diào)用的片段:
Class class = NSClassFromString(@"UIViewController");        
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString(@"viewDidLoad");
[viewController performSelector:selector];

動態(tài)替換類方法為新方法的片段:

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

正是由于OC語言具有如上片段展示的runtime時決定調(diào)用方法的特性,成就了熱更新的基礎(chǔ)原理。

具體的實現(xiàn)原理比較復(fù)雜,作者已經(jīng)開源了代碼,并且在博客中寫明了實現(xiàn)過程中的各種問題,如果有興趣的可以參閱以下鏈接:
JSPatch在github的源地址,
JSPatch詳細實現(xiàn)原理

三. 使用方法

  • 基本語法解析

下面展示一下OC代碼和熱更新的JS腳本的對應(yīng)代碼片段,例如線上 APP 有一段代碼出現(xiàn) bug 導(dǎo)致 crash:

@implementation JPTableViewController
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  NSString *content = self.dataSource[[indexPath row]];  //可能會超出數(shù)組范圍導(dǎo)致crash
  JPViewController *ctrl = [[JPViewController alloc] initWithContent:content];
  [self.navigationController pushViewController:ctrl];
}
...
@end

可以通過下發(fā)這樣一段 JS 代碼,覆蓋掉原方法,修復(fù)這個 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);
    }
  }
}, {})

除了修復(fù)一些bug之外,也可以動態(tài)的修改一些App的行為, 詳細越發(fā)可以查閱
JSPatch語法WIKI

  • SDK接入使用

前面已經(jīng)提到,騰訊已經(jīng)將JSPatch進行了商用包裝,使用方式非常簡單,進入JSPatch的官方站點,下載SDK,將SDK拖入工程,然后添加官方j(luò)avascriptcore.framework就可以按照文檔進行使用了,騰訊官方還進行了后端平臺托管,使用者在后端注冊App獲取Key,然后在App端初始化即可使用,需要熱更新的腳本也配置在后端進行下發(fā),使用非常方便簡潔,該平臺提供每天1W次下發(fā)的免費量,超過1W次需要收費服務(wù),詳細使用方式可以查閱:
JSPatch官方網(wǎng)站以及介紹

  • 源代碼引入使用

首先構(gòu)建一個簡單的demo, demo潔面只有一個按鈕,按鈕下面一個Label框,demo的簡單邏輯是,點擊按鈕之后,Label框背景顏色變?yōu)樗{色,并且顯示native code字樣。

原始代碼片段如下:

demoSource.png

初始運行界面為:

demoOne.png

點擊changeColor按鈕之后,事件響應(yīng)并執(zhí)行,運行界面變?yōu)椋?/p>

demoBlue.png

以上為native代碼的原始邏輯,下面我們開始進行動態(tài)替換:

  • 思路

我們要動態(tài)替換changeColor的點擊事件,其實就是需要動態(tài)替換clickChangeColorEvent這個函數(shù),大家可以去按照上面的JSPatch的語法wiki中去查閱詳細語法,我們這里有更簡單直接的方法,JSPatch的作者為了使用者方便,開發(fā)了
JSPatchConverter這個工具,大家可以去詳細看一下使用,也可以直接下載成品App, 我們這里使用成品App進行JS代碼生成,只需要在App的左側(cè)寫入要替換的方法,點擊convert按鈕,右邊窗口即可生成熱更新的腳本代碼,如圖:

jsconverter.png

將右邊窗口的成品JS片段自己保留出來,然后放在自建的服務(wù)器準備下發(fā)使用。

  • 本地工程準備

從github中下載JSPatch的源碼,并在源碼工程中找到JSPatch文件夾,將該文件夾拖入demo工程,如圖:

treeOne.png

同時添加蘋果官方的javascriptcore.framework,如圖:

treeTwo.png
  • 代碼實現(xiàn)

首先要在AppDelegate.h中添加對應(yīng)的頭文件:

#import "JSPatch/JPEngine.h"

繼而在iOS的App啟動入口函數(shù)中添加JSEngine的初始化:

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

以上兩步已經(jīng)完成了JSEngine的簡單的初始化過程,然后進入下一步,獲取遠端腳本的過程。 將前面在JSConverter中生成的腳本代碼自己放入自建服務(wù)器中(這一步自己尋找后端兄弟協(xié)助完成), 然后在實現(xiàn)一個獲取腳本的函數(shù),進行腳本獲取,并將執(zhí)行獲取腳本的函數(shù)放在[JPEngine startEngine]之后執(zhí)行,然后在獲取到腳本之后,執(zhí)行腳本行為,以下為完整范例:

- (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按鈕,行為已經(jīng)發(fā)生了改變:

demoYellow.png

可以看到,圖中的色塊已經(jīng)變成了黃色,圖中的提示語言也顯示JSPatch代碼進行了執(zhí)行,而這一個過程當中,native的邏輯代碼并沒有改變。

  • 歸納

上面代碼片段中的getAndRunJSPatchScript函數(shù)只是簡單的調(diào)用了接口,獲取了腳本,然后執(zhí)行。更長遠的規(guī)劃,服務(wù)端完全可以實現(xiàn)一個腳本管理服務(wù),提供一系列結(jié)構(gòu),將不同App版本,不同運營行為的腳本進行分類管理,App只需要上報自己的App基本信息,然后由服務(wù)端來返回要熱更新的腳本,更加的靈活和體系化。

總結(jié)

上述內(nèi)容可以看到JSPatch確實是一個很方便的熱更新庫,但是個人認為在App的開發(fā)過程中,大家還是應(yīng)該把質(zhì)量管控在開發(fā)階段,不可依賴于這樣的熱更新修復(fù),這樣的修復(fù)可以用于應(yīng)急來修補漏網(wǎng)的bug, 但是不建議作為App的主要邏輯開發(fā)層,因為這樣的會帶來更多的外部管理App的功能,從某種意義上來說也添加了運營的復(fù)雜性, 而且在蘋果主推的未來開發(fā)語言swift中,這種OC語言所具有的動態(tài)性將會不再存在,以這套原理來運作的JSPatch庫也就無從使用了,但是在當下的實際OC仍然是很多App的主開發(fā)語言的時代,這個熱更新修復(fù)的庫還是非常非常實用的,至于各位開發(fā)者將會對這個庫依賴多深,這就是自己定奪的問題了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容