JSPatch是最近業餘作的項目,只需在項目中引入極小的引擎,就可使用JavaScript調用任何Objective-C的原生接口,得到腳本語言的能力:動態更新APP,替換項目原生代碼修復bug。前端
用途
是否有過這樣的經歷:新版本上線後發現有個嚴重的bug,可能會致使crash率激增,可能會使網絡請求沒法發出,這時能作的只是趕忙修復bug而後提交等待漫長的AppStore審覈,再盼望用戶快點升級,付出巨大的人力和時間成本,才能完成這次bug的修復。ios
使用JSPatch能夠解決這樣的問題,只需在項目中引入JSPatch,就能夠在發現bug時下發JS腳本補丁,替換原生方法,無需更新APP即時修復bug。git
例子
01
02
03
04
05
06
07
08
09
10
|
@implementation
JPTableViewController
...
- (
void
)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(
NSIndexPath
*)indexPath
{
NSString
*content =
self
.dataSource[[indexPath row]];
//可能會超出數組範圍致使crash
JPViewController *ctrl = [[JPViewController alloc] initWithContent:content];
[
self
.navigationController pushViewController:ctrl];
}
...
@end
|
上述代碼中取數組元素處可能會超出數組範圍致使crash。若是在項目裏引用了JSPatch,就能夠下發JS腳本修復這個bug:github
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
#import 「JPEngine.m"
@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
|
01
02
03
04
05
06
07
08
09
10
11
12
|
//JS
defineClass(
"JPTableViewController"
, {
//instance method definitions
tableView_didSelectRowAtIndexPath:
function
(tableView, indexPath) {
var
row = indexPath.row()
if
(self.dataSource().length > row) {
//加上判斷越界的邏輯
var
content = self.dataArr()[row];
var
ctrl = JPViewController.alloc().initWithContent(content);
self.navigationController().pushViewController(ctrl);
}
}
}, {})
|
這樣 JPTableViewController 裏的 -tableView:didSelectRowAtIndexPath: 就替換成了這個JS腳本里的實現,在用戶無感知的狀況下修復了這個bug。web
更多的使用文檔和demo請參考github項目主頁。數組
原理
JSPatch用iOS內置的JavaScriptCore.framework做爲JS引擎,但沒有用它JSExport的特性進行JS-OC函數互調,而是經過Objective-C Runtime,從JS傳遞要調用的類名函數名到Objective-C,再使用NSInvocation動態調用對應的OC方法。詳細的實現原理以及實現過程當中遇到的各類坑和hack方法會另有文章介紹。瀏覽器
方案對比
目前已經有一些方案能夠實現動態打補丁,例如WaxPatch,能夠用Lua調用OC方法,相對於WaxPatch,JSPatch的優點是:安全
1.JS語言
JS比Lua在應用開發領域有更普遍的應用,目前前端開發和終端開發有融合的趨勢,做爲擴展的腳本語言,JS是不二之選。微信
2.符合Apple規則
JSPatch更符合Apple的規則。iOS Developer Program License Agreement裏3.3.2提到不可動態下發可執行代碼,但經過蘋果JavaScriptCore.framework或WebKit執行的代碼除外,JS正是經過JavaScriptCore.framework執行的。網絡
3.小巧
使用系統內置的JavaScriptCore.framework,無需內嵌腳本引擎,體積小巧。
4.支持block
wax在幾年前就中止了開發和維護,不支持Objective-C裏block跟Lua程序的互傳,雖然一些第三方已經實現block,但使用時參數上也有比較多的限制。
相對於WaxPatch,JSPatch劣勢在於不支持iOS6,由於須要引入JavaScriptCore.framework。另外目前內存的使用上會高於wax,持續改進中。
風險
JSPatch讓腳本語言得到調用全部原生OC方法的能力,不像web前端把能力侷限在瀏覽器,使用上會有一些安全風險:
1.若在網絡傳輸過程當中下發明文JS,可能會被中間人篡改JS腳本,執行任意方法,盜取APP裏的相關信息。能夠對傳輸過程進行加密,或用直接使用https解決。
2.若下載完後的JS保存在本地沒有加密,在未越獄的機器上用戶也能夠手動替換或篡改腳本。這點危害沒有第一點大,由於操做者是手機擁有者,不存在APP內相關信息被盜用的風險。若要避免用戶修改代碼影響APP運行,能夠選擇簡單的加密存儲。
其餘用途
JSPatch能夠動態打補丁,自由修改APP裏的代碼,理論上還能夠徹底用JSPatch實現一個業務模塊,甚至整個APP,跟wax同樣,但不推薦這麼作,由於:
- JSPatch和wax同樣都是經過Objective-C Runtime的接口經過字符串反射找到對應的類和方法進行調用,這中間的字符串處理會損耗必定的性能,另外兩種語言間的類型轉換也有性能損耗,若用來作一個完整的業務模塊,大量的頻繁來回互調,可能有性能問題。
- 開發過程當中須要用OC的思惟寫JS/Lua,喪失了腳本語言本身的特性。
- JSPatch和wax都沒有IDE支持,開發效率低。
若想動態爲APP添加模塊,目前React Native給出了很好的方案,解決了上述三個問題:
- JS/OC不會頻繁通訊,會在事件觸發時批量傳遞,提升效率。(詳見React Native通訊機制詳解)
- 開發過程無需考慮OC的感覺,聽從React框架的思想進行純JS開發就行,剩下的事情React Native幫你處理好了。
- React Native連IDE都準備好了。
因此動態添加業務模塊目前仍是推薦嘗試React Native,但React Native並不會提供原生OC接口的反射調用和方法替換,沒法作到修改原生代碼,JSPatch以小巧的引擎補足這個缺口,配合React Native用統一的JS語言讓一個原生APP時刻處於可擴展可修改的狀態。
目前JSPatch處於開發階段,穩定性和功能還存在一些問題,歡迎你們提建議/bug/PR,一塊兒來作這個項目。
——————
版權聲明:本文章在微信公衆平臺的發表權,已「獨家代理」給指定公衆賬號:iOS開發(iOSDevTips)。