JavaScriptCore初探

原:https://github.com/YanlongMa/SwiftJavaScriptCore

注:JavaScriptCore API也可以用Swift來調用败匹,本文用Objective-C來介紹。

在iOS7之前僻肖,原生應用和Web應用之間很難通信。如果你想在iOS設備上渲染HTML或者運行JavaScript卢鹦,你不得不使用UIWebView臀脏。iOS7引入了JavaScriptCore,功能更強大冀自,使用更簡單揉稚。

JavaScriptCore介紹

JavaScriptCore是封裝了JavaScript和Objective-C橋接的Objective-C API,只要用很少的代碼熬粗,就可以做到JavaScript調用Objective-C搀玖,或者Objective-C調用JavaScript。

在之前的iOS版本驻呐,你只能通過向UIWebView發(fā)送stringByEvaluatingJavaScriptFromString:消息來執(zhí)行一段JavaScript腳本灌诅。并且如果想用JavaScript調用Objective-C芳来,必須打開一個自定義的URL(例如:foo://),然后在UIWebView的delegate方法webView:shouldStartLoadWithRequest:navigationType中進行處理猜拾。

然而現在可以利用JavaScriptCore的先進功能了即舌,它可以:

運行JavaScript腳本而不需要依賴UIWebView

使用現代Objective-C的語法(例如Blocks和下標)

在Objective-C和JavaScript之間無縫的傳遞值或者對象

創(chuàng)建混合對象(原生對象可以將JavaScript值或函數作為一個屬性)

使用Objective-C和JavaScript結合開發(fā)的好處:

快速的開發(fā)和制作原型

如果某塊區(qū)域的業(yè)務需求變化的非常頻繁,那么可以用JavaScript來開發(fā)和制作原型挎袜,這比Objective-C效率更高顽聂。

團隊職責劃分

這部分參考原文吧

Since JavaScript is much easier to learn and use than Objective-C (especially if you develop a nice JavaScript sandbox), it can be handy to have one team of developers responsible for the Objective-C “engine/framework”, and another team of developers write the JavaScript that uses the “engine/framework”. Even non-developers can write JavaScript, so it’s great if you want to get designers or other folks on the team involved in certain areas of the app.

JavaScript是解釋型語言

JavaScript是解釋運行的,你可以實時的修改JavaScript代碼并立即看到結果盯仪。

邏輯寫一次芜飘,多平臺運行

可以把邏輯用JavaScript實現,iOS端和Android端都可以調用

JavaScriptCore概述

JSValue: 代表一個JavaScript實體磨总,一個JSValue可以表示很多JavaScript原始類型例如boolean, integers, doubles,甚至包括對象和函數笼沥。

JSManagedValue: 本質上是一個JSValue蚪燕,但是可以處理內存管理中的一些特殊情形,它能幫助引用技術和垃圾回收這兩種內存管理機制之間進行正確的轉換奔浅。

JSContext: 代表JavaScript的運行環(huán)境馆纳,你需要用JSContext來執(zhí)行JavaScript代碼。所有的JSValue都是捆綁在一個JSContext上的汹桦。

JSExport: 這是一個協(xié)議鲁驶,可以用這個協(xié)議來將原生對象導出給JavaScript,這樣原生對象的屬性或方法就成為了JavaScript的屬性或方法舞骆,非常神奇钥弯。

JSVirtualMachine: 代表一個對象空間,擁有自己的堆結構和垃圾回收機制督禽。大部分情況下不需要和它直接交互脆霎,除非要處理一些特殊的多線程或者內存管理問題。

JSContext / JSValue

JSVirtualMachine為JavaScript的運行提供了底層資源狈惫,JSContext為JavaScript提供運行環(huán)境睛蛛,通過

- (JSValue *)evaluateScript:(NSString*)script;

方法就可以執(zhí)行一段JavaScript腳本,并且如果其中有方法胧谈、變量等信息都會被存儲在其中以便在需要的時候使用忆肾。 而JSContext的創(chuàng)建都是基于JSVirtualMachine:

- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;

如果是使用- (id)init;進行初始化,那么在其內部會自動創(chuàng)建一個新的JSVirtualMachine對象然后調用前邊的初始化方法菱肖。

創(chuàng)建一個 JSContext 后客冈,可以很容易地運行 JavaScript 代碼來創(chuàng)建變量,做計算蔑滓,甚至定義方法:

JSContext *context = [[JSContext alloc] init];

[context evaluateScript:@"var num = 5 + 5"];

[context evaluateScript:@"var names = ['Grace', 'Ada', 'Margaret']"];

[context evaluateScript:@"var triple = function(value) { return value * 3 }"];

JSValue *tripleNum = [context evaluateScript:@"triple(num)"];

任何出自 JSContext 的值都被可以被包裹在一個 JSValue 對象中郊酒,JSValue 包裝了每一個可能的 JavaScript 值:字符串和數字遇绞;數組、對象和方法燎窘;甚至錯誤和特殊的 JavaScript 值諸如 null 和 undefined摹闽。

可以對JSValue調用toString、toBool褐健、toDouble付鹿、toArray等等方法把它轉換成合適的Objective-C值或對象。

Objective-C調用JavaScript

例如有一個"Hello.js"文件內容如下:

functionprintHello(){

}

在Objective-C中調用printHello方法:

NSString*scriptPath = [[NSBundlemainBundle] pathForResource:@"hello"ofType:@"js"];

NSString*scriptString = [NSStringstringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncodingerror:nil];

JSContext *context = [[JSContext alloc] init];

[context evaluateScript:scriptString];

JSValue *function =self.context[@"printHello"];

[function callWithArguments:@[]];

分析以上代碼:

首先初始化了一個JSContext蚜迅,并執(zhí)行JavaScript腳本舵匾,此時printHello函數并沒有被調用,只是被讀取到了這個context中谁不。

然后從context中取出對printHello函數的引用坐梯,并保存到一個JSValue中。

注意這里刹帕,從JSContext中取出一個JavaScript實體(值吵血、函數、對象)偷溺,和將一個實體保存到JSContext中蹋辅,語法均與NSDictionary的取值存值類似,非常簡單挫掏。

最后如果JSValue是一個JavaScript函數侦另,可以用callWithArguments來調用,參數是一個數組尉共,如果沒有參數則傳入空數組@[]褒傅。

JavaScript調用Objective-C

還是上面的例子,將"hello.js"的內容改為:

functionprintHello(){

print("Hello, World!");

}

這里的print函數用Objective-C代碼來實現

NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"hello" ofType:@"js"];

NSString *scriptString = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];

JSContext *context = [[JSContext alloc] init];

[context evaluateScript:scriptString];

self.context[@"print"] = ^(NSString *text) {

NSLog(@"%@", text");

};

JSValue *function = self.context[@"printHello"];

[function callWithArguments:@[]];

這里將一個Block以"print"為名傳遞給JavaScript上下文爸邢,JavaScript中調用print函數就可以執(zhí)行這個Objective-C Block樊卓。

注意這里JavaScript中的字符串可以無縫的橋接為NSString,實參"Hello, World!"被傳遞給了NSString類型的text形參杠河。

異常處理

當JavaScript運行時出現異常碌尔,會回調JSContext的exceptionHandler中設置的Block

context.exceptionHandler= ^(JSContext *context, JSValue *exception) {

NSLog(@"JS Error: %@", exception);

};

[context evaluateScript:@"function multiply(value1, value2) { return value1 * value2 "];

// 此時會打印Log "JS Error: SyntaxError: Unexpected end of script"

JSExport

JSExport是一個協(xié)議,可以讓原生類的屬性或方法稱為JavaScript的屬性或方法券敌。

看下面的例子:

@protocolItemExport

@property(strong,nonatomic)NSString*name;

@property(strong,nonatomic)NSString*description;

@end

@interfaceItem:NSObject

@property(strong,nonatomic)NSString*name;

@property(strong,nonatomic)NSString*description;

@end

注意Item類不去直接符合JSExport唾戚,而是符合一個自己的協(xié)議,這個協(xié)議去繼承JSExport協(xié)議待诅。

例如有如下JavaScript代碼

functionItem(name, description){

this.name = name;

this.description = description;

}

varitems = [];

functionaddItem(item){

items.push(item);

}

可以在Objective-C中把Item對象傳遞給addItem函數

Item *item = [[Item alloc] init];

item.name=@"itemName";

item.description=@"itemDescription";

JSValue *function = context[@"addItem"];

[function callWithArguments:@[item]];

或者把Item類導出到JavaScript環(huán)境叹坦,等待稍后使用

[self.contextsetObject:Item.selfforKeyedSubscript:@"Item"];

內存管理陷阱

Objective-C的內存管理機制是引用計數,JavaScript的內存管理機制是垃圾回收卑雁。在大部分情況下募书,JavaScriptCore能做到在這兩種內存管理機制之間無縫無錯轉換绪囱,但也有少數情況需要特別注意。

在block內捕獲JSContext

Block會為默認為所有被它捕獲的對象創(chuàng)建一個強引用莹捡。JSContext為它管理的所有JSValue也都擁有一個強引用鬼吵。并且,JSValue會為它保存的值和它所在的Context都維持一個強引用篮赢。這樣JSContext和JSValue看上去是循環(huán)引用的齿椅,然而并不會,垃圾回收機制會打破這個循環(huán)引用启泣。

看下面的例子:

self.context[@"getVersion"] = ^{

NSString*versionString = [[NSBundlemainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];

versionString = [@"version "stringByAppendingString:versionString];

JSContext *context = [JSContext currentContext];// 這里不要用self.context

JSValue *version = [JSValue valueWithObject:versionString inContext:context];

returnversion;

};

使用[JSContext currentContext]而不是self.context來在block中使用JSContext涣脚,來防止循環(huán)引用。

JSManagedValue

當把一個JavaScript值保存到一個本地實例變量上時寥茫,需要尤其注意內存管理陷阱遣蚀。 用實例變量保存一個JSValue非常容易引起循環(huán)引用。

看以下下例子纱耻,自定義一個UIAlertView妙同,當點擊按鈕時調用一個JavaScript函數:

#import

#import

@interfaceMyAlertView:UIAlertView

- (id)initWithTitle:(NSString*)title

message:(NSString*)message

success:(JSValue *)successHandler

failure:(JSValue *)failureHandler

context:(JSContext *)context;

@end

按照一般自定義AlertView的實現方法,MyAlertView需要持有successHandler膝迎,failureHandler這兩個JSValue對象

向JavaScript環(huán)境注入一個function

self.context[@"presentNativeAlert"] = ^(NSString*title,

NSString*message,

JSValue *success,

JSValue *failure) {

JSContext *context = [JSContext currentContext];

MyAlertView *alertView = [[MyAlertView alloc] initWithTitle:title

message:message

success:success

failure:failure

context:context];

[alertView show];

};

因為JavaScript環(huán)境中都是“強引用”(相對Objective-C的概念來說)的,這時JSContext強引用了一個presentNativeAlert函數胰耗,這個函數中又強引用了MyAlertView 等于說JSContext強引用了MyAlertView限次,而MyAlertView為了持有兩個回調強引用了successHandler和failureHandler這兩個JSValue,這樣MyAlertView和JavaScript環(huán)境互相引用了柴灯。

所以蘋果提供了一個JSMagagedValue類來解決這個問題卖漫。

看MyAlertView.m的正確實現:

#import"MyAlertView.h"

@interfaceXorkAlertView()

@property(strong,nonatomic) JSContext *ctxt;

@property(strong,nonatomic) JSMagagedValue *successHandler;

@property(strong,nonatomic) JSMagagedValue *failureHandler;

@end

@implementationMyAlertView

- (id)initWithTitle:(NSString*)title

message:(NSString*)message

success:(JSValue *)successHandler

failure:(JSValue *)failureHandler

context:(JSContext *)context {

self= [superinitWithTitle:title

message:message

delegate:self

cancelButtonTitle:@"No"

otherButtonTitles:@"Yes",nil];

if(self) {

_ctxt = context;

_successHandler = [JSManagedValue managedValueWithValue:successHandler];

// A JSManagedValue by itself is a weak reference. You convert it into a conditionally retained

// reference, by inserting it to the JSVirtualMachine using addManagedReference:withOwner:

[context.virtualMachineaddManagedReference:_successHandler withOwner:self];

_failureHandler = [JSManagedValue managedValueWithValue:failureHandler];

[context.virtualMachineaddManagedReference:_failureHandler withOwner:self];

}

returnself;

}

- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {

if(buttonIndex ==self.cancelButtonIndex) {

JSValue *function = [self.failureHandlervalue];

[function callWithArguments:@[]];

}else{

JSValue *function = [self.successHandlervalue];

[function callWithArguments:@[]];

}

[self.ctxt.virtualMachineremoveManagedReference:_failureHandler withOwner:self];

[self.ctxt.virtualMachineremoveManagedReference:_successHandler withOwner:self];

}

@end

分析上面例子,從外部傳入的JSValue對象在類內部使用JSManagedValue來保存赠群。

JSManagedValue本身是一個弱引用對象羊始,需要調用JSVirtualMachine的addManagedReference:withOwner:把它添加到JSVirtualMachine對象中,確保使用過程中JSValue不會被釋放

當用戶點擊AlertView上的按鈕時查描,根據用戶點擊哪一個按鈕突委,來執(zhí)行對應的處理函數,這時AlertView也隨即被銷毀冬三。 這時需要手動調用removeManagedReference:withOwner:來移除JSManagedValue匀油。

參考資料

《iOS 7 by tutorials》

https://www.bignerdranch.com/blog/javascriptcore-and-ios-7/

http://nshipster.com/javascriptcore/

https://github.com/Jobot/JSCoreByExample

https://github.com/TheHolyGrail/Zoot

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市勾笆,隨后出現的幾起案子敌蚜,更是在濱河造成了極大的恐慌,老刑警劉巖窝爪,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弛车,死亡現場離奇詭異齐媒,居然都是意外死亡,警方通過查閱死者的電腦和手機纷跛,發(fā)現死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門喻括,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忽舟,你說我怎么就攤上這事双妨。” “怎么了叮阅?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵刁品,是天一觀的道長。 經常有香客問我浩姥,道長挑随,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任勒叠,我火速辦了婚禮兜挨,結果婚禮上,老公的妹妹穿的比我還像新娘眯分。我一直安慰自己拌汇,他們只是感情好,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布弊决。 她就那樣靜靜地躺著噪舀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪飘诗。 梳的紋絲不亂的頭發(fā)上与倡,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音昆稿,去河邊找鬼纺座。 笑死,一個胖子當著我的面吹牛溉潭,可吹牛的內容都是我干的净响。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼喳瓣,長吁一口氣:“原來是場噩夢啊……” “哼别惦!你這毒婦竟也來了?” 一聲冷哼從身側響起夫椭,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤掸掸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體扰付,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡堤撵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了羽莺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片实昨。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盐固,靈堂內的尸體忽然破棺而出荒给,到底是詐尸還是另有隱情,我是刑警寧澤刁卜,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布志电,位于F島的核電站,受9級特大地震影響蛔趴,放射性物質發(fā)生泄漏挑辆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一孝情、第九天 我趴在偏房一處隱蔽的房頂上張望鱼蝉。 院中可真熱鬧,春花似錦箫荡、人聲如沸魁亦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吉挣。三九已至,卻和暖如春婉弹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背终吼。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工镀赌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人际跪。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓商佛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姆打。 傳聞我的和親對象是個殘疾皇子良姆,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容