JSPatch 是一個 iOS 動態(tài)更新框架梨睁,只需在項目中引入極小的引擎,就可以使用 JavaScript 調(diào)用任何 Objective-C 原生接口,獲得腳本語言的優(yōu)勢:為項目動態(tài)添加模塊,或替換項目原生代碼動態(tài)修復(fù) bug峻仇。
基礎(chǔ)原理
JSPatch 能做到通過 JS 調(diào)用和改寫 OC 方法最根本的原因是 Objective-C 是動態(tài)語言,OC 上所有方法的調(diào)用/類的生成都通過 Objective-C Runtime 在運行時進(jìn)行票顾,我們可以通過類名/方法名反射得到相應(yīng)的類和方法:
Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];
也可以替換某個類的方法為新的實現(xiàn):
static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"");
還可以新注冊一個類础浮,為類添加方法:
Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);
對于 Objective-C 對象模型和動態(tài)消息發(fā)送的原理已有很多文章闡述得很詳細(xì),這里就不詳細(xì)闡述了奠骄。理論上你可以在運行時通過類名/方法名調(diào)用到任何 OC 方法豆同,替換任何類的實現(xiàn)以及新增任意類。所以 JSPatch 的基本原理就是:JS 傳遞字符串給 OC含鳞,OC 通過 Runtime 接口調(diào)用和替換 OC 方法影锈。這是最基礎(chǔ)的原理,實際實現(xiàn)過程還有很多怪要打,接下來看看具體是怎樣實現(xiàn)的鸭廷。
JSPatch github地址 (iOSdemo 下載可以看到效果)
demo 的實現(xiàn)原理我用下面的一副圖來表示,邏輯更清晰,修復(fù)其他的bug 原理都差不多,只要你會寫JS 代碼 理論上是可以修復(fù)任何bug 的,還可以動態(tài)的為項目添加新的模塊
JSPatch demo的實現(xiàn)原理對比圖
demo 介紹:本來在一個控制器中添加一個btn,btn點擊事件沒有實現(xiàn);
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//1)開啟引擎
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
//2)加載js腳本(正常情況下是從服務(wù)器下載后才使用,達(dá)到動態(tài)修復(fù)app bug 的目的)
//在這個js 腳本里面使用運行時機(jī)制通過類名/方法名調(diào)用到任何 OC 方法枣抱,替換任何類的實現(xiàn)以及新增任意類,實際上調(diào)用了demo 中按鈕的點擊方法調(diào)到一個tableview控制器,demo 中Btn 的點擊跳轉(zhuǎn)到tableview的方法沒有實現(xiàn),是app啟動的時候加載js,js實現(xiàn)了btn的點擊并且跳轉(zhuǎn)到tableview控制器,js到底是如何實現(xiàn)映射是呢?
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
//執(zhí)行js腳本
[JPEngine evaluateScript:script];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
JPViewController *rootViewController = [[JPViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
[[UINavigationBar appearance] setBackgroundImage:nil forBarMetrics:UIBarMetricsCompact];
return YES;
}
JSPatch 實現(xiàn)熱更新app 修復(fù)線上app的bug 的核心是會寫js 腳本下面是js語言實現(xiàn)攔截按鈕的點擊事件 并且跳轉(zhuǎn)到一個TableView
defineClass('JPViewController', { ?handleBtn: function(sender) { ? ?var tableViewCtrl = JPTableViewController.alloc().init() ? ?self.navigationController().pushViewController_animated(tableViewCtrl, YES) ?}})defineClass('JPTableViewController : UITableViewController', ['data'], {
dataSource: function() {
var data = self.data();
if (data) return data;
var data = [];
for (var i = 0; i < 20; i ++) {
data.push("cell from js " + i);
}
self.setData(data)
return data;
},
numberOfSectionsInTableView: function(tableView) {
return 1;
},
tableView_numberOfRowsInSection: function(tableView, section) {
return self.dataSource().length;
},
tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("cell")
if (!cell) {
cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(0, "cell")
}
cell.textLabel().setText(self.dataSource()[indexPath.row()])
return cell
},
tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
return 60
},
tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
var alertView = require('UIAlertView').alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("Alert",self.dataSource()[indexPath.row()], self, "OK", ?null);
alertView.show()
},
alertView_willDismissWithButtonIndex: function(alertView, idx) {
console.log('click btn ' + alertView.buttonTitleAtIndex(idx).toJS())
}
})
參考文獻(xiàn):
______
- 作者開發(fā)經(jīng)驗總結(jié)的文章推薦,持續(xù)更新學(xué)習(xí)心得筆記
[Runtime 10種用法(沒有比這更全的了)](http://www.reibang.com/p/3182646001d1)
[成為iOS頂尖高手,你必須來這里(這里有最好的開源項目和文章)](http://www.reibang.com/p/8dda0caf47ea)
[iOS逆向Reveal查看任意app 的界面](http://www.reibang.com/p/060745d5ecc2)
[JSPatch (實時修復(fù)App Store bug)學(xué)習(xí)(一)](http://www.reibang.com/p/344db07a2374)
[iOS 高級工程師是怎么進(jìn)階的(補充版20+點)](http://www.reibang.com/p/1f2907512046)
[擴(kuò)大按鈕(UIButton)點擊范圍(隨意方向擴(kuò)展哦)](http://www.reibang.com/p/ce2d3191224f)
[最簡單的免證書真機(jī)調(diào)試(原創(chuàng))](http://www.reibang.com/p/c724e6282819)
[通過分析微信app,學(xué)學(xué)如何使用@2x,@3x圖片](http://www.reibang.com/p/99f1f924ae45)
[TableView之MVVM與MVC之對比](http://www.reibang.com/p/d690b5d97201)
[使用MVVM減少控制器代碼實戰(zhàn)(減少56%)](http://www.reibang.com/p/f85363c82ea1)
[ReactiveCocoa添加cocoapods 配置圖文教程及坑總結(jié)](http://www.reibang.com/p/66f0c7e1ced8)
- 作者開發(fā)經(jīng)驗總結(jié)的文章推薦,持續(xù)更新學(xué)習(xí)心得筆記
[Runtime 10種用法(沒有比這更全的了)](http://www.reibang.com/p/3182646001d1)
[成為iOS頂尖高手辆床,你必須來這里(這里有最好的開源項目和文章)](http://www.reibang.com/p/8dda0caf47ea)
[iOS逆向Reveal查看任意app 的界面](http://www.reibang.com/p/060745d5ecc2)
[JSPatch (實時修復(fù)App Store bug)學(xué)習(xí)(一)](http://www.reibang.com/p/344db07a2374)
[iOS 高級工程師是怎么進(jìn)階的(補充版20+點)](http://www.reibang.com/p/1f2907512046)
[擴(kuò)大按鈕(UIButton)點擊范圍(隨意方向擴(kuò)展哦)](http://www.reibang.com/p/ce2d3191224f)
[最簡單的免證書真機(jī)調(diào)試(原創(chuàng))](http://www.reibang.com/p/c724e6282819)
[通過分析微信app,學(xué)學(xué)如何使用@2x,@3x圖片](http://www.reibang.com/p/99f1f924ae45)
[TableView之MVVM與MVC之對比](http://www.reibang.com/p/d690b5d97201)
[使用MVVM減少控制器代碼實戰(zhàn)(減少56%)](http://www.reibang.com/p/f85363c82ea1)
[ReactiveCocoa添加cocoapods 配置圖文教程及坑總結(jié)](http://www.reibang.com/p/66f0c7e1ced8)