JSPatch學習筆記(二)

這次筆記中主要描述的有:

  • JSPath原理理解(學習作者大牛博客)
  • JSPatch使用的時機
  • AppDelegate中更新JS文件后何時生效
  • 其他時機手動更新JS文件的效果
  • JS調(diào)用OC方法中的幾個坑
  • JS腳本文件的版本控制管理
  • 更多思考

1. JSPatch原理淺談

JSPatch用iOS內(nèi)置的JavaScriptCore.framework作為JS引擎,但沒有用它JSExport的特性進行JS-OC函 數(shù)互調(diào),而是通過Objective-C Runtime,從JS傳遞要調(diào)用的類名函數(shù)名到Objective-C,再使用NSInvocation動態(tài)調(diào)用對應的OC方法。

詳細原理介紹可見作者博客:JSPatch 實現(xiàn)原理詳解

另外JSPatch已經(jīng)有商業(yè)化的平臺jspatch.com,可以使用里面的SDK,通過這個平臺上傳的js腳本都存儲在七牛云。

2. JSPatch使用的時機

確切地說應該是在經(jīng)過怎樣的流程之后開始載入調(diào)用JS腳本。
在董鉑然的博客:JSPatch使用小記中有這樣的一套方案:

這個方案的特點:

  1. 添加了上次請求的時間,避免多余的網(wǎng)絡請求
  2. 把更新js腳本的代碼放在了applicationDidBecomeActive:方法中,避免程序在后臺的時候也進行不必要的腳本更新檢查。
  3. 對js文件進行code校驗,避免傳輸過程中被修改。實際使用中應對js腳本文件進行加密。(作者的博客中也建議用RSA等非對稱加密對文件進行加密傳輸)
  4. 連續(xù)崩潰次數(shù)的判斷,能夠做到程序自我選擇性修復

3. AppDelegate中更新JS文件后何時生效

當經(jīng)過本文第二點中流程之后,本地載入了最新的js腳本文件,那么程序什么時候會使用這個腳本中的代碼呢?
為此我寫了demo測試:

AppDelegate中的代碼

判斷本地是否存在hotfix.js文件:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    NSString *docuPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *hotfixPath = [docuPath stringByAppendingPathComponent:@"hotfix.js"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:hotfixPath]) {
        [JPEngine startEngine];
        NSString *script = [NSString stringWithContentsOfFile:hotfixPath encoding:NSUTF8StringEncoding error:nil];
        [JPEngine evaluateScript:script];
    }
    
    return YES;
}

檢測是否需要更新:

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    
    [HotFixManager checkUpdateCompleteHandle:^(BOOL status, NSString *response, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (!status){
                if (!error) {
                    NSLog(@"沒有更新");
                } else {
                    NSLog(@"%@", error.userInfo);
                }
                return ;
            }
            NSLog(@"Hotfix文件更新成功");
            [JPEngine startEngine];
            NSString *script = [NSString stringWithContentsOfFile:response encoding:NSUTF8StringEncoding error:nil];
            [JPEngine evaluateScript:script];
            
        });
    }];
   
}
  • 首先第一點可以確定的是:
    判斷本地是否存在js文件需要加載的代碼不能夠?qū)懺诰W(wǎng)絡請求是否需要更新的回調(diào)中
    原因:
    首先看下我的hotfix.js文件中的代碼
defineClass('JSPatchController', [/*新增的屬性*/'updateLabel'], {//實例方法
            
            viewDidLoad: function() {
//            self.ORIGviewDidLoad()
            self.setTitle("測試JS1")
            self.view().addSubview(self.getUpdateLabel())
            },
            
            //實現(xiàn)Label的getter方法
            getUpdateLabel: function() {
            var _updateLabel = self.updateLabel()
            if (!_updateLabel) {
                _updateLabel = require('UILabel').alloc().init()
                _updateLabel.setFrame({x:50, y:100, width:100, height:30})
                _updateLabel.setText("點擊按鈕更新JS代碼--->")
                _updateLabel.setFont(require('UIFont').systemFontOfSize(15))
                _updateLabel.setTextColor(require('UIColor').redColor())
                _updateLabel.sizeToFit()
                self.setUpdateLabel(_updateLabel)
            }
            return _updateLabel
            },
})

代碼中是有添加了updateLabel這樣一個屬性,并添加到JSPatchController的視圖中,其中JSPatchController為window下navigationController的rootViewController。

當我嘗試在檢查更新的block沒有更新的區(qū)塊中執(zhí)行

[JPEngine startEngine];
 NSString *script = [NSString stringWithContentsOfFile:hotfixPath encoding:NSUTF8StringEncoding error:nil]; 
[JPEngine evaluateScript:script]

發(fā)現(xiàn)JSPatchController并沒有添加上updateLabel,起初以為是沒有回到主線程中更新視圖,經(jīng)過嘗試并不是。當執(zhí)行NSURLSessionDataTask的網(wǎng)絡請求后,會新開啟一個線程。而主線程繼續(xù)執(zhí)行編譯JSPatchController,當新的線程中的網(wǎng)絡請求完成了,然后加載了js腳本文件,此時已經(jīng)不能再對JSPatchController動態(tài)添加updateLabel了。

  • 第二點:didFinishLaunchingWithOptions: 的執(zhí)行優(yōu)先級是高于applicationDidBecomeActive:方法的,檢查本地的js文件應發(fā)在前者中。

  • 第三點: applicationDidBecomeActive: 方法中檢測需要更新并下載了js腳本文件,但是只有下一次啟動App的時候才能生效 在根控制器中的動態(tài)修改代碼只能下一次啟動App的時候才能生效。

4. 其他時機手動更新JS文件的效果

其他時機這里指的是例如UIControl事件,點擊按鈕后更新js腳本,那么這個腳本文件中的代碼何時生效呢?
在demo中的JSPatchController中 “更新JS” 這個按鈕,點擊執(zhí)行的代碼如下:

- (IBAction)updateJS:(UIButton *)sender {
    sender.selected = !sender.isSelected;
    NSString *url = sender.isSelected ? jsfile1 : jsfile2;
    [sender setTitle:@"當前JS1" forState:UIControlStateSelected];
    [sender setTitle:@"當前JS2" forState:UIControlStateNormal];
    
    NSLog(@"下載的是%@",url);
    [HotFixManager downLoadHotFixJSfileWithURL:url completeHandle:^(BOOL status, id response, NSError *error) {
        if (!status){
            NSLog(@"下載出錯\n%@", error.userInfo);
            return ;
        }
        NSLog(@"Hotfix文件更新成功");
        [JPEngine startEngine];
        NSString *script = [NSString stringWithContentsOfFile:response encoding:NSUTF8StringEncoding error:nil];
        [JPEngine evaluateScript:script];
    }];
}

點按按鈕有兩個js腳本文件可以進行切換,兩個js文件中的區(qū)別部分為對下一個控制器中的lable文字和navigation title的控制

jsfile1:

defineClass('SecondViewController', {
            viewDidLoad: function() {
            self.ORIGviewDidLoad()
            var label = self.myLabel()
            label.setText("這是在JS1中修改的文字")
            self.setTitle("JS1推出的頁面")
            }
            })

jsfile2:

defineClass('SecondViewController', {
            viewDidLoad: function() {
            self.ORIGviewDidLoad()
            var label = self.myLabel()
            label.setText("這是在JS2中修改的文字")
            self.setTitle("JS2推出的頁面")
            }
            })

測試結(jié)果是:更新的js腳本中對之后的頁面的js修復代碼是可以生效的,但是對之前的頁面跟同級的頁面是沒有效果的。

其實這些,只要搞清楚原理就都能知道緣由和規(guī)律。

5. JS調(diào)用OC方法中的幾個坑

  • 在js中 NSNumber不需要在處理,可直接當數(shù)值使用。
  • NSRang 初始化:
    var range = {location: 0, length: senderName.length()};
  • 無論變量還是方法,單下劃線全部改為雙下劃線
  • CGRect 取寬高, 直接rect.width, 不用rect.size.width。其他結(jié)構(gòu)體類似
  • js 中 YES 為 ture,NO 為false
  • oc對象轉(zhuǎn)js對象可操作toJS(),js對象轉(zhuǎn)oc對象暫時沒找到方法。
    js內(nèi)創(chuàng)建的字典為js對象,傳入oc方法無效
  • jspatch 不支持變參方法,如stringWithFormat:,可用js字符串方法或NSMutableString代替。

6. JS腳本文件的版本控制管理

一般使用的js腳本文件都是從服務器下發(fā)過來,服務器的接口需要返回時候需要更新的參數(shù),需要則下載于當前程序版本號對應版本的js腳本文件。對于多個版本的項目來說,js腳本文件最好也進行版本控制避免出錯,可以在服務器單獨建立js腳本文件的Git倉庫來進行管理。

7. 更多思考

  • JS熱修復的代碼在下一次更新中應當使用原生的代碼替換,不能超過一個版本。避免對JSPatch有過多的依賴
  • 使用JS語法來調(diào)用OC的方法,沒有代碼自動補全顯得非常吃力。作者bang開發(fā)了Xcode插件:JSPatchX,然而Xcode8不支持插件了。。。
  • 在一個很復雜的方法中,僅中間某一行代碼需要修改,就要將整個方法用JS重寫一遍,推介作者開發(fā)的Objective-C轉(zhuǎn)JavaScript代碼工具JSPatch Convertor,但一些復雜的語法還是要人工修正
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容