17/03/08更新
有不少小伙伴反應(yīng)蘋果發(fā)送了郵件要求去除項(xiàng)目中用于動(dòng)態(tài)改變應(yīng)用的代碼 ,看來 JSPatch 要被禁止使用了
Your app, extension, and/or linked framework appears to contain code designed explicitly with the capability to change your app’s behavior or functionality after App Review approval, which is not in compliance with section 3.3.2 of the Apple Developer Program License Agreement and App Store Review Guideline 2.5.2. This code, combined with a remote resource, can facilitate significant changes to your app’s behavior compared to when it was initially reviewed for the App Store. While you may not be using this functionality currently, it has the potential to load private frameworks, private methods, and enable future feature changes.
This includes any code which passes arbitrary parameters to dynamic methods such as dlopen(), dlsym(), respondsToSelector:, performSelector:, method_exchangeImplementations(), and running remote scripts in order to change app behavior or call SPI, based on the contents of the downloaded script. Even if the remote resource is not intentionally malicious, it could easily be hijacked via a Man In The Middle (MiTM) attack, which can pose a serious security vulnerability to users of your app.
Please perform an in-depth review of your app and remove any code, frameworks, or SDKs that fall in line with the functionality described above before submitting the next update for your app for review.
Best regards,
App Store Review
JSPatch 平臺(tái)關(guān)于蘋果警告的解決方案 --bang
前言
App 上線后可能存在測(cè)試時(shí)未發(fā)現(xiàn)的 bug,影響用戶使用,利用 JSPatch 在 App 不進(jìn)行版本迭代的情況下進(jìn)行 bug 修復(fù)。
什么是 JSPatch ?
JSPatch 是一個(gè)開源項(xiàng)目(Github鏈接),只需要在項(xiàng)目里引入極小的引擎文件,就可以使用 JavaScript 調(diào)用任何 Objective-C 的原生接口,替換任意 Objective-C 原生方法。目前主要用于下發(fā) JS 腳本替換原生 Objective-C 代碼,實(shí)時(shí)修復(fù)線上 bug。
除了修復(fù) bug,JSPatch 也可以用于動(dòng)態(tài)運(yùn)營,實(shí)時(shí)修改線上 APP 行為,或動(dòng)態(tài)添加功能。
什么是 JSPatch 平臺(tái)?
JSPatch 需要使用者有一個(gè)后臺(tái)可以下發(fā)和管理腳本,并且需要處理傳輸安全等部署工作,JSPatch 平臺(tái)幫你做了這些事,提供了腳本后臺(tái)托管,版本管理,保證傳輸安全等功能,讓你無需搭建一個(gè)后臺(tái),無需關(guān)心部署操作,只需引入一個(gè) SDK 即可立即使用 JSPatch。
引用自 JSPatch 的官方文檔(文檔鏈接),下面講一下 JSPatch 的使用。
JSPatch 使用
新建一個(gè)項(xiàng)目

然后在 ViewController.m 中寫一段會(huì)造成崩潰的代碼(這里只是參考)
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic , strong) NSArray *arr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@" , self.arr[2]);
}
- (NSArray *)arr {
if (!_arr) {
_arr = [NSArray array];
}
return _arr;
}
集成 SDK
1、通過 cocoapods 導(dǎo)入
在 podfile 中添加命令:
pod 'JSPatchPlatform', :git => 'https://github.com/bang590/JSPatchPlatform.git'
再執(zhí)行 pod install 即可。
2、手動(dòng)導(dǎo)入
下載 SDK 后解壓,將** JSPatchPlatform.framework** 拖入項(xiàng)目中,勾選 "Copy items if needed",并確保 "Add to targets" 勾選了相應(yīng)的 target。

pods 導(dǎo)入或手動(dòng)導(dǎo)入SDK后添加依賴框架:TARGETS -> Build Phases -> Link Binary With Libraries -> + 添加libz.dylib(Xcode7之后是libz.tbd)和 JavaScriptCore.framework。

測(cè)試使用
1、測(cè)試本地腳本
本地測(cè)試 AppDelegate.m 按如下寫法即可,SDK 提供了+testScriptInBundle 方法用于開發(fā)狀態(tài)下測(cè)試。
#import "AppDelegate.h"
// 引入頭文件
#import <JSPatchPlatform/JSPatch.h>
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/*
用于發(fā)布前測(cè)試腳本。先把腳本放入項(xiàng)目中,調(diào)用后,會(huì)在當(dāng)前項(xiàng)目的 bundle 里尋找 main.js 文件執(zhí)行
測(cè)試完成后請(qǐng)刪除,改為調(diào)用 +startWithAppKey: 和 +sync
*/
[JSPatch testScriptInBundle];
return YES;
}
然后在我們的 demo 中新建一個(gè) empty 文件,叫 main.js,注意這是 JSPatch 平臺(tái)規(guī)范,js 腳本文件名必須是 main.js。


完成之后是這樣

現(xiàn)在我們就可以通過在 main.js 寫 js 修復(fù) demo 中的 bug,代碼如下:
require('NSArray');
defineClass('ViewController', {
viewDidLoad: function() {
self.super().viewDidLoad();
self.setArr(["1","2","阿西吧","3"]);
var str = self.arr().objectAtIndex(2);
console.log("JSPatch調(diào)用" , str);
},
});
運(yùn)行,OK

2、線上版本測(cè)試
熱修復(fù)針對(duì)的是線上版本,所以本地測(cè)試只是驗(yàn)證可行性,重要的還是線上 bug 的修復(fù)。下面我們進(jìn)行線上測(cè)試。
到 JSPatch官網(wǎng) 注冊(cè),登錄,我的 App,添加 App,獲取 app key,添加 App 版本,發(fā)布補(bǔ)丁



提交之后點(diǎn)進(jìn)去就可以進(jìn)行補(bǔ)丁發(fā)布:

注:有關(guān)開發(fā)預(yù)覽、灰度下發(fā)、條件下發(fā)請(qǐng)查看官方文檔開發(fā)預(yù)覽和灰度與條件下發(fā)了解。補(bǔ)丁發(fā)布之后,對(duì)應(yīng)版本的 APP 會(huì)請(qǐng)求下載這個(gè)腳本保存在本地,以后每次啟動(dòng)都會(huì)執(zhí)行這個(gè)腳本。至此線上 bug 修復(fù)完成。
現(xiàn)在只是發(fā)布了補(bǔ)丁,而我們的測(cè)試 demo 里的代碼還沒有修改,所以還不能進(jìn)行線上測(cè)試。下面修改我們的測(cè)試代碼。
-application:didFinishLaunchingWithOptions:調(diào)用+startWithAppKey:方法,參數(shù)為之前獲得的 appKey。接著調(diào)用+sync方法檢查更新。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//[JSPatch testScriptInBundle]; // 用于發(fā)布前測(cè)試腳本,測(cè)試完成后請(qǐng)刪除,改為調(diào)用 +startWithAppKey: 和 +sync
[JSPatch startWithAppKey:@"210a38abb83a9689"];
[JSPatch sync];
return YES;
}
注意:+testScriptInBundle不能與+startWithAppKey:一起調(diào)用,+testScriptInBundle只用于本地測(cè)試,測(cè)試完畢后需要去除,項(xiàng)目中的 main.js 文件也要?jiǎng)h除(可拷貝一份至桌面留作上傳的補(bǔ)丁使用)。另外,通過 JSPatch 平臺(tái)上傳的腳本文件都會(huì)保存在七牛云存儲(chǔ)上,而七牛云存儲(chǔ)的下載使用的是 http 協(xié)議,因此需要在項(xiàng)目的 info.plist 文件中添加如下字段(這個(gè)應(yīng)該都知道的...忽略)

OK,現(xiàn)在我們來運(yùn)行 demo

和本地測(cè)試一樣線上測(cè)試bug也修復(fù)成功。
修改/刪除 js 腳本
若后續(xù)需要對(duì)這個(gè)腳本進(jìn)行修改,可以重新上傳新的腳本,APP 客戶端會(huì)在請(qǐng)求時(shí)發(fā)現(xiàn)腳本已更新,下載最新腳本覆蓋原來的,下次啟動(dòng)時(shí)執(zhí)行。
若想直接取消某個(gè) APP 版本的 JS 腳本補(bǔ)丁,可以直接在 APP 版本界面刪除此 APP 版本,APP 客戶端會(huì)在請(qǐng)求時(shí)發(fā)現(xiàn)腳本已被刪除,即刻刪除本地 JS 腳本文件,下次啟動(dòng)時(shí)不再加載。
補(bǔ)充
JSPatch 作者@bang提供了一個(gè)轉(zhuǎn)換工具 JSPatch Converto,可以將 OC 轉(zhuǎn)換為 js 。

此外,@bang 大神還給我們準(zhǔn)備了JSPatch XCode代碼自動(dòng)補(bǔ)全插件,讓我們使用 JSPatch 更得心應(yīng)手。

結(jié)語
對(duì) JSPatch 研究有限,這里只是介紹了 JSPatch 的基礎(chǔ)用法,有錯(cuò)誤或不當(dāng)?shù)牡胤綒g迎指正、交流。如果你想學(xué)習(xí)更多 JSPatch 的用法可以到 JSPatch 平臺(tái)使用文檔了解。如果你想知道 JSPatch 實(shí)現(xiàn)原理,看這里:JSPatch 實(shí)現(xiàn)原理詳解。
本文 demo 后續(xù)會(huì)上傳 GitHub,鏈接留在留言中。