iOS中使用JSPatch框架使Objective-C與JavaScript代碼交互
JSPatch是GitHub上一個(gè)開源的框架,其可以通過Objective-C的run-time機(jī)制動(dòng)態(tài)的使用JavaScript調(diào)用與替換項(xiàng)目中的Objective-C屬性與方法。其框架小巧,代碼簡潔,并且通過系統(tǒng)的JavaScriptCore框架與Objective-C進(jìn)行交互,這使其在安全性和審核風(fēng)險(xiǎn)上都有很強(qiáng)的優(yōu)勢。Git源碼地址:https://github.com/bang590/JSPatch。
一、從一個(gè)官方的小demo看起
通過cocoapods將JSPath集成進(jìn)一個(gè)Xcode工程中,在AppDelegate類的中編寫如下代碼:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //開始初始化引擎 [JPEngine startEngine]; //讀取js文件 NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"]; NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; //運(yùn)行js文件 [JPEngine evaluateScript:script]; self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds]; self.window.rootViewController = [[ViewController alloc]init]; [self.window addSubview:[self genView]]; [self.window makeKeyAndVisible]; return YES; } - (UIView *)genView { UIView * view= [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 320)]; view.backgroundColor = [UIColor redColor]; return view; }
在工程中添加一個(gè)js文件,編寫如下:
require('UIView, UIColor, UILabel') //要替換函數(shù)的類 defineClass('AppDelegate', { //替換函數(shù) //要替換函數(shù)的名稱 genView: function() { var view = self.ORIGgenView(); view.setBackgroundColor(UIColor.greenColor()) var label = UILabel.alloc().initWithFrame(view.frame()); label.setText("JSPatch"); label.setTextAlignment(1); view.addSubview(label); return view; } });
運(yùn)行工程,可以看到genView方法被替換成了js文件中的方法,原本紅色的視圖被修改成了綠色。
二、使用JavaScript代碼向Objective-C中修改或添加方法
JSPatch引擎中支持3中方式進(jìn)行JavaScript代碼的調(diào)用,分別是使用JavaScript字符串進(jìn)行代碼運(yùn)行,讀取本地的JavaScript文件進(jìn)行代碼運(yùn)行和獲取網(wǎng)絡(luò)的JavaScript文件進(jìn)行代碼運(yùn)行。例如,如果想要通過JavaScript代碼在項(xiàng)目中彈出一個(gè)警告框,在Objective-C代碼中插入如下代碼:
- (void)viewDidLoad { [super viewDidLoad]; // ‘\'符用于進(jìn)行換行 [JPEngine evaluateScript:@"\ var alertView = require('UIAlertView').alloc().init();\ alertView.setTitle('Alert');\ alertView.setMessage('AlertView from js'); \ alertView.addButtonWithTitle('OK');\ alertView.show(); \ "]; }
開發(fā)者也可以動(dòng)態(tài)在Objective-C類文件中添加方法,例如在ViewController類中編寫如下:
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; [JPEngine startEngine]; NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"]; NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; [JPEngine evaluateScript:script]; [self performSelectorOnMainThread:@selector(creatView) withObject:nil waitUntilDone:nil]; }
JavaScript文件代碼如下:
require('UIView, UIColor, UILabel') defineClass('ViewController', { // replace the -genView method creatView: function() { var view = UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100}); view.setBackgroundColor(UIColor.greenColor()); var label = UILabel.alloc().initWithFrame({x:0, y:0, width:100, height:100}); label.setText("JSPatch"); label.setTextAlignment(1); view.addSubview(label); self.view().addSubview(view) } });
除了上面的代碼,在ViewController.m文件中沒有編寫任何其他的方法,運(yùn)行工程,可以看到程序并沒有崩潰,ViewController執(zhí)行了creatView方法。
通過上面的示例,我們發(fā)現(xiàn)使用JSPatch可以做一些十分有趣的事。對于iOS應(yīng)用來說,通過官方渠道AppStore進(jìn)行應(yīng)用程序的發(fā)布要通過人工審核,有時(shí)這個(gè)審核周期會(huì)非常長,如果在開發(fā)者在編寫代碼時(shí)留下了一些小漏洞,應(yīng)用一旦上線,若要修改掉這個(gè)bug就十分艱難了。有了JSPatch,我們可以想象,如果可以定位到線上應(yīng)用有問題的方法,使用JS文件來修改掉這個(gè)方法,這將是多么cool的一件事,事實(shí)上,JSPatch的主要用途也是可以實(shí)現(xiàn)線上應(yīng)用極小問題的hotfix。
三、JavaScript與Objective-C交互的基礎(chǔ)方法
要使用JSPatch來進(jìn)行Objective-C風(fēng)格的方法編寫,需要遵守一些JavaScript與Objective-C交互的規(guī)則。
1.在JavaScript文件中使用Objective-C類
在編寫JavaScript代碼時(shí)如果需要用到Objective-C的類,必須先對這個(gè)類進(jìn)行require引用,例如,如果需要使用UIView這個(gè)類,需要在使用前進(jìn)行如下引用:
require('UIView')
同樣也可以一次對多個(gè)Objective-C類進(jìn)行引用:
require('UIView, UIColor, UILabel')
還有一種更加簡便的寫法,直接在使用的時(shí)候?qū)ζ溥M(jìn)行引用:
require('UIView').alloc().init()
2.在JavaScript文件中進(jìn)行Objective-C方法的調(diào)用
在進(jìn)行Objective-C方法的調(diào)用時(shí),分為兩種,一種是調(diào)用類方法,一種是調(diào)用類的對象方法。
調(diào)用類方法:通過類名打點(diǎn)的方式來調(diào)用類方法,格式類似如下,括號內(nèi)為參數(shù)傳遞:
UIColor.redColor()
調(diào)用實(shí)例方法:通過對象打點(diǎn)的方式調(diào)用類的實(shí)例方法,格式如下,括號內(nèi)為參數(shù)傳遞:
view.addSubview(label)
對于Objective-C中的多參數(shù)方法,轉(zhuǎn)化為JavaScript將參數(shù)分割的位置以_進(jìn)行分割,參數(shù)全部放入后面的括號中,以逗號分割,示例如下:
view.setBackgroundColor(UIColor.colorWithRed_green_blue_alpha(0,0.5,0.5,1))
對于Objective-C類的屬性變量,在JavaScript中只能使用getter與setter方法來訪問,示例如下:
label.setText("JSPatch")
提示:如果原Objective-C的方法中已經(jīng)包含了_符號,則在JavaScript中使用__代替。
3.在JavaScript中操作與修改Objective-C類
JSPatch的最大應(yīng)用是在應(yīng)用運(yùn)行時(shí)動(dòng)態(tài)的操作和修改類。
重寫或者添加類的方法:
在JavaScript中使用defineClass來定義和修改類中的方法,其編寫格式如下所示:
/* classDeclaration:要添加或者重寫方法的類名 字符串 如果此類不存在 則會(huì)創(chuàng)建新的類 instanceMethods:要添加或者重寫的實(shí)例方法 {} classMethods:要添加或者重寫的類方法 {} */ defineClass(classDeclaration, instanceMethods, classMethods)
示例如下:
defineClass('ViewController', { // replace the -genView method newFunc: function() { //編寫實(shí)例方法 self.view().setBackgroundColor(UIColor.redColor()) } },{ myLoad:function(){ //編寫類方法 } } )
如果在重寫了類中的方法后要調(diào)用原方法,需要使用ORIG前綴,示例如下:
defineClass('ViewController', { // replace the -genView method viewDidLoad: function() { //編寫實(shí)例方法 self.ORIGviewDidLoad() } } )
對于Objective-C中super關(guān)鍵字調(diào)用的方法,在JavaScript中可以使用self.super()來調(diào)用,例如:
defineClass('ViewController', { // replace the -genView method viewDidLoad: function() { //編寫實(shí)例方法 self.super().viewDidLoad() } } )
同樣JSPatch也可以為類添加臨時(shí)屬性,用于在方法間參數(shù)傳遞,使用set_Prop_forKey()來添加屬性,使用getProp()來獲取屬性,注意,JSPatch添加的屬性不能使用Objective-C的setter與getter方法訪問,如下:
defineClass('ViewController', { // replace the -genView method viewDidLoad: function() { //編寫實(shí)例方法 self.super().viewDidLoad() self.setProp_forKey("JSPatch", "data") }, touchesBegan_withEvent(id,touch){ self.getProp("data") self.view().setBackgroundColor(UIColor.redColor()) } } )
關(guān)于為類添加協(xié)議的遵守,和Objective-C中遵守協(xié)議的方式一致,如下:
defineClass("ViewController2: UIViewController <UIAlertViewDelegate>", { viewDidAppear: function(animated) { var alertView = require('UIAlertView') .alloc() .initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles( "Alert", "content", self, "OK", null ) alertView.show() }, alertView_clickedButtonAtIndex:function(alertView, buttonIndex) { console.log('clicked index ' + buttonIndex) } })
四、JavaScript與Objective-C交互的幾種常用類型
1.結(jié)構(gòu)體
在Objective-C代碼中,我們經(jīng)常會(huì)使用到結(jié)構(gòu)體,JSPatch中原生支持的結(jié)構(gòu)體有如下幾種:CGPoint,CGSize,CGRect,NSRange。并且這幾種結(jié)構(gòu)體在進(jìn)行界面操作時(shí)也會(huì)經(jīng)常使用到。
對于CGRect類型,JavaScript使用如下代碼創(chuàng)建:
var view = require('UIView').alloc().init() view.setFrame({x:100,y:100,width:100,height:100})
對于CGPoint類型,JavaScript使用如下代碼創(chuàng)建:
view.setCenter({x:200,y:200})
對于CGSize類型,JavaScript使用如下代碼創(chuàng)建:
var size = {width:200,height:200} view.setFrame({x:100,y:100,width:size.width,height:size.height})
對于NSRange類型,JavaScript使用如下代碼創(chuàng)建:
var range = {location: 0, length: 1}
2.選擇器Selector
對于Objective-C中的方法選擇器Selector,在JavaScript中使用字符串的形式創(chuàng)建,例如:
self.performSelector_withObject("func:", 1)
3.關(guān)于空對象
在JavaScript中,null與undefined都對應(yīng)于Objective-C中的nil,Objective-C中的NSNull空對象,在JavaScript中使用nsnull來代替。
4.在Objective-C與JavaScript中進(jìn)行block的交互
在JavaScript與Objective-C進(jìn)行block交互有兩種方式,一種是在JavaScript文件中調(diào)用Objective-C中的block,一種是將JavaScript文件中的函數(shù)塊作為block參數(shù)傳遞給Objective-C。
在JavaScript文件中使用Objective-C中的block十分簡單,因?yàn)镴avaScript中沒有block的概念,Objective-C會(huì)被自動(dòng)轉(zhuǎn)換為函數(shù),示例如下:
Objective-C:
typedef void(^block)(NSString * str); @interface ViewController () @end @implementation ViewController -(block)getBlock{ block block = ^(NSString * str){NSLog(@"%@",str);}; return block; } @end
JavaScript:
defineClass("ViewController", { viewDidAppear: function(animated) { var func = self.getBlock() func("123") } })
在JavaScript文件中將func作為參數(shù)block傳遞給Objective-C就復(fù)雜一些,需要使用block()方法進(jìn)行包裝,例如:
Objective-C:
@interface ViewController () @end @implementation ViewController -(void)run:(void(^)(NSString * str))block{ block(@"123"); } @end
JavaScript:
defineClass("ViewController", { viewDidAppear: function(animated) { //run 方法中需要傳入一個(gè)block self.run(block("NSString*",function(str){console.log(str)})) } })
在使用block()方法對JavaScript中的Func進(jìn)行包裝時(shí),block(param1,param2)有兩個(gè)參數(shù),第1個(gè)參數(shù)設(shè)置func中的參數(shù)類型,如果有多個(gè)參數(shù),使用逗號分割;第2個(gè)參數(shù)為func函數(shù)體。
注意:在block()包裝的func中不可以使用self指針,如果需要使用self,需要在block外進(jìn)行臨時(shí)變量的轉(zhuǎn)換,示例如下:
defineClass("ViewController", { viewDidAppear: function(animated) { //run 方法中需要傳入一個(gè)block var slf = self self.run(block("NSString*", function(str){ console.log(str) slf.log(str) })) } })
在JavaScript中分別使用__weak()與__strong來聲明弱引用與強(qiáng)引用對象,例如:
var slf = __weak(self) var stgSef = __strong(self)
5.關(guān)于GCD與枚舉
在JSPatch中,可以使用如下JavaScript代碼來調(diào)用GCD方法:
//阻塞當(dāng)前線程一定時(shí)間 dispatch_after(1.0, function(){ }) //為主線程添加異步任務(wù) dispatch_async_main(function(){ }) //為主線程添加同步任務(wù) dispatch_sync_main(function(){ }) //向全局隊(duì)列中添加任務(wù) dispatch_async_global_queue(function(){ }) JSPatch中不可以直接使用Objective-C中定義的枚舉,但是可以用其枚舉的真實(shí)值進(jìn)行傳遞。例如: //UIControlEventTouchUpInside的值是1<<6 btn.addTarget_action_forControlEvents(self, "handleBtn", 1<<6);
相關(guān)文章
Objective-C實(shí)現(xiàn)無限循環(huán)輪播器
這篇文章主要介紹了Objective-C實(shí)現(xiàn)無限循環(huán)輪播器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05iOS開發(fā)中實(shí)現(xiàn)郵件和短信發(fā)送的簡單示例
這篇文章主要介紹了iOS開發(fā)中實(shí)現(xiàn)郵件和短信發(fā)送的簡單示例,編程語言依然是傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-09-09iOS對數(shù)組進(jìn)行排序的實(shí)例代碼
本文通過實(shí)例代碼給大家講解了ios對數(shù)組進(jìn)行排序的實(shí)例方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-08-08iOS實(shí)現(xiàn)漸變按鈕Gradient Button的方法示例
這篇文章主要給大家介紹了關(guān)于iOS實(shí)現(xiàn)漸變按鈕Gradient Button的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對各位iOS開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08