JSPath原理

一、用途

是否有過這樣的經歷:新版本上線後發現有個嚴重的bug,可能會導致crash率激增,可能會使網絡請求無法發出,這時能做的只是趕緊修復bug然後提交等待漫長的AppStore審覈,再盼望用戶快點升級,付出巨大的人力和時間成本,才能完成此次bug的修復。

使用JSPatch可以解決這樣的問題,只需在項目中引入JSPatch,就可以在發現bug時下發JS腳本補丁,替換原生方法,無需更新APP即時修復bug

二、原理

JSPatchiOS內置的JavaScriptCore.framework作爲JS引擎,但沒有用它JSExport的特性進行JS-OC函數互調,而是通過Objective-CRuntime,從JS傳遞要調用的類名函數名到Objective-C,再使用NSInvocation動態調用對應的OC方法。

三、通過實例瞭解JSPatch使用過程。

首先簡單介紹一下這個實例要實現的功能

新建一個工程,有三個視圖

視圖1 ,rootViewController,

視圖2,firstViewControoler,

視圖3,sceondViewControll. (可以給firstViewControoler和SecondViewController分別加上不同的標題和背景色用以便好區分)

源代碼中,視圖1中有一個按鈕,點擊按鈕進入視圖2,代碼如下

- (void)enterNextView:(id)sender

{

    FirstViewController* vc =[[FirstViewController alloc]init];

    [self.navigationControllerpushViewController:vc animated:YES];

}

現在我們要定義一個js文件,通過JSPatch框架來實現,點擊按鈕進入的視圖不是視圖2而是視圖3.

1.首先需要引入JSPatch插件。

通過CocoaPod引入JSPatch插件:

pod ‘JSPatch’

工程引入系統框架 JavaScriptCore.framework

 

2.編寫js代碼(demo.js)。

require('SecondViewController')//聲明引用的object c中的類

 

defineClass(rootViewController, {

            enterNextView: function(sender)  {

   var vc = SecondViewController.alloc().init()

   self.navigationController().pushViewController_animated(vc, YES)

 }

})

//defineClass覆蓋rootViewController裏原來的按鈕點擊事件enterNextView

3.加載js文件

 

-       (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

-       {

   // Override point for customization after application launch.

   

   [JPEngine startEngine];//啓動JP引擎

  //加載本地demo.js文件。

   NSString *sourcePath = [[NSBundle mainBundle]pathForResource:@"demo" ofType:@"js"];

   NSString *script = [NSString stringWithContentsOfFile:sourcePathencoding:NSUTF8StringEncoding error:nil];

   [JPEngine evaluateScript:script];

   

   return YES;

}

運行程序 ,這時點擊按鈕,執行的不是rootViewController裏的enterNextView函數,而是demo.js文件裏的enterNextView,界面也就從視圖1進入了視圖3界面而不是視圖2界面。

 

注:此處爲了方便實現加載本地的js文件 ,真正應用的時候,需要把這個js文件放在服務器上程序啓動時通過以下代碼加載。

[JPEngine startEngine]; //啓動JP引擎

 //下載服務器端js文件並加載到JPEngine

    [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/bugfix.JS"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    if (script) {

      [JPEngine evaluateScript:script];

    }

四,JSPatch Loader使用

JSPatchLoader 負責根據版本號向服務端拉取 JSPatch代碼,並對代碼進行 RSA校驗/解壓/執行,整個校驗原理在JSPatch部署安全策略 這篇文章裏詳細說明,不再複述。安全策略可參考http://blog.cnbang.net/tech/2879/.

1.安裝

拷貝 Loader/ 目錄下的文件到你的項目

2.配置

1)設 JPLoader.h的rootUrl 爲你的服務器地址。腳本文件在服務器的存放路徑是${rootUrl}/${appVersion}/${patchFile}

2)自行生成 RSA 公鑰私鑰,替換 JPLoader.h 裏的 publicKey 和 tools/pack.php 裏的 privateKey。

3腳本打包

JSPatch腳本文件規則:可以有多個 js 文件,腳本內可以調用 include() 接口包含,沒有目錄層級,必須包含一個 main.js 文件作爲入口。

在命令行使用 Loader/tools/pack.php腳本打包 JS 文件,由用戶放到自己的服務器上給客戶端下載。

示例

$ php pack.php main.js other.js

會在當前目錄生成 v1.zip 文件,打包了所有 js 文件幷包含了校驗文件。也可以在最後通過 -o 指定輸出文件名:

$ php pack.php main.js -o v2

腳本文件名代表當前 patch 版本,與後續的+updateToVersion:callback: 接口相關。

4 加載

下載/更新腳本

客戶端在得知服務端腳本有更新時,調用 +updateToVersion:callback:接口下載對應版本的腳本。至於如何得知服務端腳本更新可以自行定義,可以另外加個請求每次喚醒時詢問服務器,也可以在 APP 原有的請求里加上這個信息。

舉個例子,客戶端當前 App 版本號爲1.0,上述配置 rootUrl 變量配爲 http://localhost/JSPatch/,服務端告訴客戶端最新腳本版本號爲2,於是調用 [JPLoader updateToVersion:2callback:nil],這時會去請求 http://localhost/JSPatch/1.0/v2.zip這個文件並解壓驗證,保存到本地目錄等待執行。

執行腳本

通過 +run 接口執行已下載到本地的 JSPatch 腳本文件,建議在程序啓動的 -application:didFinishLaunchingWithOptions: 裏第一句調用這個接口,防止調用後執行 JSPatch 腳本過程中其他線程同時在執行相關代碼,導致意想不到的問題。

5測試

在腳本文件還沒打包上傳到服務器前,可以先把文件加入項目工程 bundle 進行測試,加入後調用 +runTestScriptInBundle 就會執行項目工程裏的 main.js 文件,並且 JS 腳本里 include() 接口也可以正常使用。

(參考https://github.com/bang590/JSPatch/wiki/JSPatch-Loader-使用文檔

五、總結

要在項目中使用JSPatch實現動態更新ios,需要以下步聚

1.引入JSPatch和JavaScriptCore.framework

2.實現js文件並放到服務端供終端加載

(具本的實現js到object c轉換的語法和需要注意事項目可參考文檔https://github.com/bang590/JSPatch/wiki)

3.AppDelegate中加載服務端的js文件

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    [JPEngine startEngine];

    [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/bugfix.JS"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    if (script) {

      [JPEngine evaluateScript:script];

    }

}];

   ….

    return YES;

}

@end

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章