JSPatch 是什么
JSPatch 是一個開源項目(Github鏈接),只需要在項目里引入極小的引擎文件,就可以使用 JavaScript 調用任何 Objective-C 的原生接口,替換任意 Objective-C 原生方法。目前主要用于下發 JS 腳本替換原生 Objective-C 代碼,實時修復線上 bug。
JSPatch平臺又是什么鬼
JSPatch需要你自己搞一個服務器管理、下發腳本,還要處理安全問題,高并發問題,煩死你。JSPatch平臺封裝了SDK,你只需要繼承SDK就可以省去一堆的麻煩。
JSPatch 需要使用者有一個后臺可以下發和管理腳本,并且需要處理傳輸安全等部署工作,JSPatch 平臺幫你做了這些事,提供了腳本后臺托管,版本管理,保證傳輸安全等功能,讓你無需搭建一個后臺,無需關心部署操作,只需引入一個 SDK 即可立即使用 JSPatch。
JSPatch 平臺的 SDK 在核心代碼的基礎上增加了向平臺請求腳本/傳輸解密/版本管理等功能,只用于這個平臺。
通過 JSPatch 平臺上傳的腳本文件都會保存在七牛云存儲上,客戶端 APP 只跟七牛服務器通訊,支持高并發,CDN分布全國,速度和穩定性有保證。
SDK接入
這種問題不要問我,注冊一個賬號,建一個app獲取到appid,然后接入SDK完事兒。至于你是用cocoapods還是手動接入,全憑個人喜好。SDK接入
API主要方法
+startWithAppKey:
傳入在平臺申請的 appKey,啟動 JSPatch SDK。在-application:didFinishLaunchingWithOptions:
開頭初始化第三方庫的時候一并調用初始化。
+sync
與 JSPatch 平臺后臺同步,詢問是否有 patch 更新,如果有更新會自動下載并執行。
每調用一次 +sync 就會請求一次后臺,如果app啟動的時候檢查一次就OK的話就在
-application:didFinishLaunchingWithOptions:
調用一次。如果實時性要求高,就在
-applicationDidBecomeActive:
的時候調用。
+setupLogger:
SDK打一些請求和執行的log,默認是NSLog()
輸出,如果app有自己的日志系統,并且希望自己的日志系統拿到這些log,則在+startWithAppKey之前調用這個方法。
+testScriptInBundle
寫好的腳本上線前總要測試一下吧,就是用這個方法。需要把main.js文件拖到項目中,并且不要調用+startWithAppKey:方法。
注意?。。。簻y試完成一定要刪除main.js,血淋淋的教訓是,如果不刪除,線上的腳本down下來之后,無法確定會執行哪個main.js,莫名其妙的問題,并且很難找到。切記切記
+setupCallback:
JSPatch 執行過程中的事件回調,在以下事件發生時會調用傳入的 block:
typedef NS_ENUM(NSInteger, JPCallbackType){
JPCallbackTypeUnknow = 0,
JPCallbackTypeRunScript = 1, //執行腳本
JPCallbackTypeUpdate = 2, //腳本有更新
JPCallbackTypeUpdateDone = 3, //已拉取新腳本
JPCallbackTypeCondition = 4, //條件下發
JPCallbackTypeGray = 5, //灰度下發
};
例如
[JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
switch (type) {
case JPCallbackTypeUpdate: {
NSLog(@"updated %@ %@", data, error);
break;
}
case JPCallbackTypeRunScript: {
NSLog(@"run script %@ %@", data, error);
break;
}
default:
break;
}
}];
+setupUserData:
定義用戶屬性,在+sync:
之前調用,用于條件下發,可以用來做AB測試。什么是AB測試?自己去Google啊...
[JSPatch setupUserData:@{
@"userId": user.userId,
@"location": user.location,
@"gender":user.gender,
@"age":user.age
}];

發布補丁的時候選擇條件下發,寫入相應的條件就可以實現條件下發。例如圖中性別是女,年齡小于35歲的用戶顯示特定的內容。還可以選擇手機系統的版本。
+setupDevelopment
開發者預覽模式,可以在 debug 模式下測試補丁效果。
[JSPatch startAppWithKey:@""];
#ifdef DEBUG
[JSPatch setupDevelopment];
#endif
[JSPatch sync];
灰度下發
這個功能太實用了,選擇灰度下發可以按照比例灰度和人數灰度下發。比例灰度例如隨機挑選 30% 的設備生效;人數灰度比如只安裝1000臺設備。應用場景:
- 先下發一批看看效果,如果OK就全量下發
- 只對部分用戶下發,顯示特定的效果(和條件下發類似)
實戰
扯了這么多終于到實戰了。
背景:
- 項目已經集成了SDK
- 注冊過了平臺賬號
- 已經注冊了APP獲得了appkey
- 已經上線了集成過JSPatch SDK的app
- 這個上線的版本出現了大量的crash,crash率很高,不馬上解決,老板就會馬上解決你...
線上的代碼是這樣的,數組訪問越界了
@implementation DSHomeViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *content = self.dataSource[[indexPath row]]; //可能會超出數組范圍導致crash
DSGoodsViewController *controller = [[DSGoodsViewController alloc] initWithContent:content];
[self.navigationController pushViewController:controller];
}
@end
修改源代碼
修改后的代碼
@implementation DSHomeViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(self.dataSource.length > indexPath.row){
NSString *content = self.dataSource[indexPath.row];
DSGoodsViewController *controller = [[DSGoodsViewController alloc] initWithContent:content];
[self.navigationController pushViewController:controller];
}
}
@end
編寫補丁腳本
打開JSPatch代碼轉換器,原生代碼轉化為JS代碼

轉化成功了,但是要數組,需要修改一下。常見問題 修改之后的腳本,為了能夠知道腳本運行,第一行加上log
console.log('JSPatch Run Success');
require("DSGoodsViewController");
defineClass("DSHomeViewController", {
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var row = indexPath.row();
if (self.dataSource().length() > row) {
var content = self.dataSource()[row];
var controller = DSGoodsViewController.alloc().initWithContent(content);
self.navigationController().pushViewController(controller);
}
}
}, {});
測試腳本
寫好之后的腳本存為main.js放到項目中,在-application:didFinishLaunchingWithOptions:
方法中打開測試
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//[JSPatch startWithAppKey:myAPPKey];
//[JSPatch sync];
[JSPatch testScriptInBundle];
….
}
編譯運行會看到控制臺log輸出JSPatch Run Success
然后......怎么能少了調試呢
在Safari中斷點調試
開啟 Safari 調試菜單
Safari -> 偏好設置 -> 高級 -> 勾選[在菜單欄中顯示“開發”菜單]
啟動app進行調試
啟動APP -> Safari -> 開發 -> 選擇你的機器 -> JSContext
在 iOS8 下,JSPatch 支持使用 Safari 自帶的調試工具對 JS 腳本進行斷點調試,界面大致長這樣

上傳腳本
- 在平臺上新建一個線上的版本
- 把調試通過的腳本main.js上傳到這個線上的版本
- 選擇全量下發(因為要搞定crash)
- 刪除本地的main.js
- 好了,等著下發之后crash率降下來,飯碗保住了
常見的問題
- 不能用
NSLog('xx')
,應該用console.log('xx')
- get property 記得加括號,例如
self.navigationItem()
,而不是self.navigationItem
- 私有成員變量要用
self.valueForKey()
和self.setValue_forKey()
接口存取 - block 里不能直接使用 self,應該在block外定義var myself = self;
未完待續。。。