一站式搞定 JSPatch 熱修復(fù)

最近接觸到熱修復(fù), 確實(shí)能解燃眉之急, 非常好用, 故分享給大家. 這里只講 JSPatch, 這個(gè)是現(xiàn)在最熱門最好用的框架, 用起來超級(jí)簡(jiǎn)單, 非常感謝 bang590 的貢獻(xiàn).

JSPatch 是一個(gè)開源項(xiàng)目, 只需要在項(xiàng)目里引入極小的引擎文件, 就可以使用 JavaScript 調(diào)用任何 Objective-C 的原生接口, 替換任意 Objective-C 原生方法. 目前主要用于下發(fā) JS 腳本替換原生 Objective-C 代碼, 實(shí)時(shí)修復(fù)線上 bug.

項(xiàng)目集成

[Github][1] 下載后, 按照[操作文檔][2]操作就可以輕松集成, 摘錄 bang590 Github 簡(jiǎn)要步驟如下:
[1]:https://github.com/bang590/JSPatch
[2]:https://github.com/bang590/JSPatch/blob/master/README-CN.md

  • 拷貝 JSPatch/ 目錄下的三個(gè)文件 JSEngine.m / JSEngine.h / JSPatch.js 到項(xiàng)目里
  • #import "JPEngine.h"
  • 調(diào)用 [JPEngine startEngine]
  • 通過 [JPEngine evaluateScript:@""] 接口執(zhí)行 JavaScript。
  • 直接把下面代碼拷貝到 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法中即可, app 運(yùn)行后只調(diào)用一次, 即每次運(yùn)行 app 只更新一次 JS 修復(fù)
  • 如若要更新即時(shí)性, 可以把方法放到 - (void)applicationWillEnterForeground:(UIApplication *)application 這樣每次 app 從后臺(tái)進(jìn)入前臺(tái), 都會(huì)拉取 JS 修復(fù)文件
// 方法一: 從網(wǎng)絡(luò)拉回js腳本執(zhí)行

[JPEngine startEngine];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:kDownloadPath]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [JPEngine evaluateScript:script];
}];

// 上面代碼 kDownloadPath 換成你自己的 JS 文件地址即可
// 每次都從網(wǎng)絡(luò)拉取, 雖然文件小, 但也受限也網(wǎng)絡(luò)狀態(tài), 不太理想.
// 方法二: 先下載到本地, 再?gòu)谋镜匚募A中讀取

NSURLSession *session = [NSURLSession sharedSession]; 
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:kDownloadPath] completionHandler:^(NSURL * _Nullable location,NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"location: %@", location);

// 下載任務(wù)會(huì)把下載的資源存放到臨時(shí)文件夾tmp下. block結(jié)束后, 就會(huì)自動(dòng)刪除.
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *path = [docPath stringByAppendingPathComponent:@"demo.js"];
NSLog(@"path: %@", path);// 拷貝路徑在 Finder ->前往 ->前往文件夾 可看到已下載文件

// 測(cè)試了會(huì)有緩存, 且不能把原有的 JS 文件覆蓋, 故要先移除
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
    [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}

// 故把下載數(shù)據(jù)移動(dòng)到document下
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:path]error:nil];
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    [JPEngine startEngine];
    [JPEngine evaluateScriptWithPath:path];
    }]; 
}];    
[task resume];

// 上面代碼 kDownloadPath 換成你自己的 JS 文件地址即可

實(shí)際不需要每次都拉取, 該方法也只是暫緩措施, 下次迭代版本必須把上次 JS 修復(fù)的用原生解決, 這時(shí)需要有一個(gè)后臺(tái)可以下發(fā) JS 下載路徑和管理腳本, 并且需要處理傳輸安全等部署工作.

JS 文件

JS 文件創(chuàng)建

  • 使用 xcode 創(chuàng)建 JS 文件


    xcode 創(chuàng)建 JS 文件.png
  • 使用 Sublime Text 工具創(chuàng)建 JS 文件, 同樣后綴保存為 .js 即可

JS 語(yǔ)法

  • 在 defineClass 里定義 OC 已存在的方法即可覆蓋, 語(yǔ)法如下:
defineClass(classDeclaration, [properties,] instanceMethods, classMethods)

@param classDeclaration: 字符串,類名/父類名和Protocol
@param properties: 新增property,字符串?dāng)?shù)組,可省略
@param instanceMethods: 要添加或覆蓋的實(shí)例方法
@param classMethods: 要添加或覆蓋的類方法

// 例如:
require('UIDevice');
defineClass("ViewController", {
    viewDidLoad: function() {
        var model = UIDevice.currentDevice().model();
        console.log(model);
        if (UIDevice.currentDevice().systemVersion().floatValue() >= 9) {
            console.log("9.0版本");
        } else {
            console.log("其他版本");
        }
        console.log("js 打印, 腳本號(hào): 1.0, 替換實(shí)例成功");
    }
}, {
    test: function() {
        console.log("js 打印, 腳本號(hào): 1.0, 替換類方法成功");
    }
});
  • 要替換多個(gè)方法, 都要重新寫 defineClass("類名", [新增屬性,], {實(shí)例方法}, {類方法}), 屬性可以省略.

  • 只有類方法或者實(shí)例方法, 就留空大括號(hào) {}, 如只需修改類方法: defineClass("類名", {}, {類方法}).

  • 在方法名前加 ORIG 即可調(diào)用未覆蓋前的 OC 原方法:

  •   viewDidLoad: function() {
       self.ORIGviewDidLoad();
     },
    

- 在 JS 里面判斷是否為空要判斷 false

- ```java
    var url = "";
    var rawData = NSData.dataWithContentsOfURL(NSURL.URLWithString(url));
    if (rawData != null) {} //這樣判斷是錯(cuò)誤的
    應(yīng)該如下判斷:
    if (!rawData){}
    在JSPatch.js源碼里_formatOCToJS方法對(duì)undefined,null,isNil轉(zhuǎn)換成了false。
  • Objective-C 里的常量/枚舉/宏/全局變量不能直接在 JS 上使用
  • 更多語(yǔ)法見 JSPatch 基礎(chǔ)語(yǔ)法, 也可以借助 JSPatch 代碼轉(zhuǎn)換器, 當(dāng)然轉(zhuǎn)換器不是萬能了, 還需要自己細(xì)心檢查.
  • JSPatch 替換的是整個(gè)方法, 哪怕只有一行代碼需要修復(fù), 整個(gè)方法都需要重寫成 JS 代碼. 倡導(dǎo)使用敏捷開發(fā)的思想, 類似于主邏輯或者是功能模塊入口的方法可以抽的更細(xì), 這樣即使需要修改, 成本也不會(huì)太大.

版本管理

公司搭建后臺(tái)

自己公司搭建后臺(tái), 除了下發(fā)拉取 JS 的地址外, 還可以加入一些參數(shù), 比如: 版本控制, 指定修復(fù)某 iOS 版本等等, 條件根據(jù)需求定, 跟一般請(qǐng)求無異, 就不敘述了.

七牛云平臺(tái)

JS 文件也可以存放到七牛云上, 七牛云同樣提供版本控制, 這樣自己公司后臺(tái)省很多事, 只需寫一個(gè)接口, 而且有一定的免費(fèi)額度, 足夠用了.

七牛云平臺(tái).png
七牛云使用流程
  • 注冊(cè)完七牛云賬號(hào)后, 點(diǎn)擊添加對(duì)象存儲(chǔ)創(chuàng)建儲(chǔ)存空間, 訪問控制注意選公開空間, 這樣外界才能訪問到 JS 文件.
添加對(duì)象存儲(chǔ).png
創(chuàng)建儲(chǔ)存空間.png
內(nèi)存管理.png
  • 上傳文件后, 復(fù)制外鏈接就是 JS 文件路徑
上傳文件.png
  • 需要注意的是七牛云平臺(tái)文件是有緩存的, 所以在上傳 JS 文件的命名不要和前面重復(fù), 不然下發(fā)后看到結(jié)果會(huì)是上一次同名文件效果, 緩存時(shí)間可以在空間設(shè)置里設(shè)置
文件緩存時(shí)間.png

JSPatch 平臺(tái)

不想搭建后臺(tái), 可以使用 JSPatch 平臺(tái), 也不用把 JS 文件上傳到七牛云, 直接上傳到 JSPatch 平臺(tái)即可, 功能很多, 還提供條件下發(fā), 平臺(tái)文檔介紹已經(jīng)非常詳細(xì)了, 這里就不再贅述了.

不過平臺(tái)是需要收費(fèi)的

JSPatch平臺(tái)收費(fèi).png
!!!使用 JSPatch 平臺(tái)注意點(diǎn)
  • 注意在 JSPatch 平臺(tái)的規(guī)范里,JS 腳本的文件名必須是 main.js。
  • 自定義 RSA 密鑰, 按照提示在終端輸入命令后, 生成的文件在主目錄下:
RSA 文件.png
  • 按照文檔那樣導(dǎo)入 public_key 太麻煩了, 而且容易出錯(cuò), 可以把 rsa_public_key.pem 文件拖入工程中, 再執(zhí)行下面代碼就可以:

NSString *keyPath = [[NSBundle mainBundle] pathForResource:@"rsa_public_key" ofType:@"pem"];
NSString *publicKey = [NSString stringWithContentsOfFile:keyPath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"publicKey: %@", publicKey);
[JSPatch setupRSAPublicKey:publicKey];
//下方是 JSPatch 啟動(dòng)代碼
[JSPatch startWithAppKey:@"19ed6339k440fa3ab"];

ifdef DEBUG

[JSPatch setupDevelopment];

endif

[JSPatch sync];

    

######集成錯(cuò)誤錄

- 若使用 XCode8 接入,需要在項(xiàng)目 Capabilities 打開 Keychain Sharing 開關(guān),否則在模擬器下載腳本后會(huì)出現(xiàn) `decompress error, md5 didn't match` 錯(cuò)誤(真機(jī)無論是否打開都沒問題)

- pod JSPatch 平臺(tái) SDK 完成并添加依賴庫(kù), 啟動(dòng) `startWithAppKey` 和 `sync` 后報(bào)錯(cuò)

- ```objc
duplicate symbol _OBJC_METACLASS_$_JPEngine in:
    /Users/issuser/Library/Developer/Xcode/DerivedData/ViewController-ajurqnqqgeaehfajwnvxgpblrcmz/Build/Intermediates/ViewController.build/Debug-iphonesimulator/ViewController.build/Objects-normal/x86_64/JPEngine.o
    /Users/issuser/Library/Developer/Xcode/DerivedData/ViewController-ajurqnqqgeaehfajwnvxgpblrcmz/Build/Products/Debug-iphonesimulator/JSPatch/libJSPatch.a(JPEngine.o)
ld: 11 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

 //原因是工程里有手動(dòng)導(dǎo)入 JSPatch.h JSPatch.m 和 JSPatch.js 文件, 和 cocoapods 沖突了

小結(jié)

JSPatch 熱修復(fù)集成簡(jiǎn)單吧, 難點(diǎn)在 JS 語(yǔ)法上, 沒有語(yǔ)法提示, 寫的時(shí)候更要細(xì)心.
如果沒有效果的話, 檢查 JS 語(yǔ)法是否正確, 也可以通過 Safari 的調(diào)試工具對(duì) JS 進(jìn)行斷點(diǎn)調(diào)試, 詳見 JS 斷點(diǎn)調(diào)試, 還有是否執(zhí)行之前緩存的文件.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • JSPatch是一個(gè)可以在線修復(fù)bug的輕量級(jí)框架,項(xiàng)目中嵌入這個(gè)框架可以讓你的app具有熱更新的能力。你可以通過...
    daixunry閱讀 6,156評(píng)論 5 38
  • 一:關(guān)于JSPatch JSPatch : 是一個(gè)iOS動(dòng)態(tài)更新框架,只需在項(xiàng)目中引入極小的引擎,就可以使用Jav...
    dahaibushen閱讀 546評(píng)論 0 0
  • JSPatch是什么 JSPatch是一個(gè)開源項(xiàng)目,只需要在項(xiàng)目里引入極小的引擎文件,就可以使用 JavaScri...
    ImmortalSummer閱讀 2,626評(píng)論 7 11
  • 前言 IOS熱修復(fù)一直是關(guān)注的重點(diǎn)之一。由于appstore的審核上架機(jī)制的局限,新發(fā)布的版本往往要等待很長(zhǎng)時(shí)間才...
    o翻滾的牛寶寶o閱讀 3,442評(píng)論 17 42
  • 你說,我無感情 你說,我只是泥瓦拼 你說,我只是木頭加鐵釘 我說,不管風(fēng)吹雨淋 不管是天晴雨陰 不管是雪積冰凌 我...
    舒己懷_Frank閱讀 453評(píng)論 16 35

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