OC與JS交互之JavaScriptCore

JavaScriptCore初探

在iOS7之前,原生應(yīng)用和Web應(yīng)用之間很難通信崭庸。如果你想在iOS設(shè)備上渲染HTML或者運(yùn)行JavaScript属划,你不得不使用UIWebView涧至。iOS7引入了JavaScriptCore,功能更強(qiáng)大逼友,使用更簡(jiǎn)單精肃。

JavaScriptCore介紹

JavaScriptCore是封裝了JavaScript和Objective-C橋接的Objective-C API,只要用很少的代碼帜乞,就可以做到JavaScript調(diào)用Objective-C司抱,或者Objective-C調(diào)用JavaScript。

在之前的iOS版本黎烈,你只能通過(guò)向UIWebView發(fā)送stringByEvaluatingJavaScriptFromString:消息來(lái)執(zhí)行一段JavaScript腳本习柠。并且如果想用JavaScript調(diào)用Objective-C,必須打開(kāi)一個(gè)自定義的URL(例如:foo://)照棋,然后在UIWebView的delegate方法webView:shouldStartLoadWithRequest:navigationType中進(jìn)行處理资溃。

然而現(xiàn)在可以利用JavaScriptCore的先進(jìn)功能了,它可以:
運(yùn)行JavaScript腳本而不需要依賴UIWebView
使用現(xiàn)代Objective-C的語(yǔ)法(例如Blocks和下標(biāo))
在Objective-C和JavaScript之間無(wú)縫的傳遞值或者對(duì)象
創(chuàng)建混合對(duì)象(原生對(duì)象可以將JavaScript值或函數(shù)作為一個(gè)屬性)
使用Objective-C和JavaScript結(jié)合開(kāi)發(fā)的好處:
快速的開(kāi)發(fā)和制作原型:
如果某塊區(qū)域的業(yè)務(wù)需求變化的非常頻繁烈炭,那么可以用JavaScript來(lái)開(kāi)發(fā)和制作原型溶锭,這比Objective-C效率更高。
團(tuán)隊(duì)職責(zé)劃分:
這部分參考原文吧

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是解釋型語(yǔ)言:
JavaScript是解釋運(yùn)行的符隙,你可以實(shí)時(shí)的修改JavaScript代碼并立即看到結(jié)果趴捅。
邏輯寫一次,多平臺(tái)運(yùn)行:
可以把邏輯用JavaScript實(shí)現(xiàn)霹疫,iOS端和Android端都可以調(diào)用

JavaScriptCore概述

JSValue: 代表一個(gè)JavaScript實(shí)體拱绑,一個(gè)JSValue可以表示很多JavaScript原始類型例如boolean, integers, doubles,甚至包括對(duì)象和函數(shù)丽蝎。
JSManagedValue: 本質(zhì)上是一個(gè)JSValue猎拨,但是可以處理內(nèi)存管理中的一些特殊情形,它能幫助引用技術(shù)和垃圾回收這兩種內(nèi)存管理機(jī)制之間進(jìn)行正確的轉(zhuǎn)換。
JSContext: 代表JavaScript的運(yùn)行環(huán)境红省,你需要用JSContext來(lái)執(zhí)行JavaScript代碼额各。所有的JSValue都是捆綁在一個(gè)JSContext上的。
JSExport: 這是一個(gè)協(xié)議类腮,可以用這個(gè)協(xié)議來(lái)將原生對(duì)象導(dǎo)出給JavaScript臊泰,這樣原生對(duì)象的屬性或方法就成為了JavaScript的屬性或方法蛉加,非常神奇蚜枢。
JSVirtualMachine: 代表一個(gè)對(duì)象空間,擁有自己的堆結(jié)構(gòu)和垃圾回收機(jī)制针饥。大部分情況下不需要和它直接交互厂抽,除非要處理一些特殊的多線程或者內(nèi)存管理問(wèn)題。

JSContext / JSValue
JSVirtualMachine為JavaScript的運(yùn)行提供了底層資源丁眼,JSContext為JavaScript提供運(yùn)行環(huán)境筷凤,通過(guò)

  • (JSValue *)evaluateScript:(NSString *)script;
    方法就可以執(zhí)行一段JavaScript腳本,并且如果其中有方法苞七、變量等信息都會(huì)被存儲(chǔ)在其中以便在需要的時(shí)候使用藐守。 而JSContext的創(chuàng)建都是基于JSVirtualMachine:
  • (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
    如果是使用- (id)init;進(jìn)行初始化,那么在其內(nèi)部會(huì)自動(dòng)創(chuàng)建一個(gè)新的JSVirtualMachine對(duì)象然后調(diào)用前邊的初始化方法蹂风。

創(chuàng)建一個(gè) JSContext 后卢厂,可以很容易地運(yùn)行 JavaScript 代碼來(lái)創(chuàng)建變量,做計(jì)算惠啄,甚至定義方法:

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 的值都被可以被包裹在一個(gè) JSValue 對(duì)象中慎恒,JSValue 包裝了每一個(gè)可能的 JavaScript 值:字符串和數(shù)字;數(shù)組撵渡、對(duì)象和方法融柬;甚至錯(cuò)誤和特殊的 JavaScript 值諸如 null 和 undefined。

可以對(duì)JSValue調(diào)用toString趋距、toBool粒氧、toDouble、toArray等等方法把它轉(zhuǎn)換成合適的Objective-C值或?qū)ο蟆?/p>

Objective-C調(diào)用JavaScript
例如有一個(gè)"Hello.js"文件內(nèi)容如下:

function printHello() {
}

在Objective-C中調(diào)用printHello方法:

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];

JSValue *function = self.context[@"printHello"];
[function callWithArguments:@[]];
分析以上代碼:

首先初始化了一個(gè)JSContext节腐,并執(zhí)行JavaScript腳本外盯,此時(shí)printHello函數(shù)并沒(méi)有被調(diào)用,只是被讀取到了這個(gè)context中铜跑。

然后從context中取出對(duì)printHello函數(shù)的引用门怪,并保存到一個(gè)JSValue中。

注意這里锅纺,從JSContext中取出一個(gè)JavaScript實(shí)體(值掷空、函數(shù)、對(duì)象),和將一個(gè)實(shí)體保存到JSContext中坦弟,語(yǔ)法均與NSDictionary的取值存值類似护锤,非常簡(jiǎn)單。

最后如果JSValue是一個(gè)JavaScript函數(shù)酿傍,可以用callWithArguments來(lái)調(diào)用烙懦,參數(shù)是一個(gè)數(shù)組,如果沒(méi)有參數(shù)則傳入空數(shù)組@[]赤炒。

JavaScript調(diào)用Objective-C
還是上面的例子氯析,將"hello.js"的內(nèi)容改為:

function printHello() {
print("Hello, World!");
}

這里的print函數(shù)用Objective-C代碼來(lái)實(shí)現(xiàn)

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:@[]];

這里將一個(gè)Block以"print"為名傳遞給JavaScript上下文,JavaScript中調(diào)用print函數(shù)就可以執(zhí)行這個(gè)Objective-C Block莺褒。

注意這里JavaScript中的字符串可以無(wú)縫的橋接為NSString掩缓,實(shí)參"Hello, World!"被傳遞給了NSString類型的text形參。

異常處理
當(dāng)JavaScript運(yùn)行時(shí)出現(xiàn)異常遵岩,會(huì)回調(diào)JSContext的exceptionHandler中設(shè)置的Block

context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"JS Error: %@", exception);
};

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

// 此時(shí)會(huì)打印Log "JS Error: SyntaxError: Unexpected end of script"
JSExport
JSExport是一個(gè)協(xié)議你辣,可以讓原生類的屬性或方法稱為JavaScript的屬性或方法。

看下面的例子:

@protocol ItemExport <JSExport>
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *description;
@end
@interface Item : NSObject <ItemExport>
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *description;
@end

注意Item類不去直接符合JSExport尘执,而是符合一個(gè)自己的協(xié)議舍哄,這個(gè)協(xié)議去繼承JSExport協(xié)議。

例如有如下JavaScript代碼

function Item(name, description) {
this.name = name;
this.description = description;
}
var items = [];
function addItem(item) {
items.push(item);
}

可以在Objective-C中把Item對(duì)象傳遞給addItem函數(shù)

Item *item = [[Item alloc] init];
item.name = @"itemName";
item.description = @"itemDescription";
JSValue *function = context[@"addItem"];
[function callWithArguments:@[item]];
或者把Item類導(dǎo)出到JavaScript環(huán)境誊锭,等待稍后使用

[self.context setObject:Item.self forKeyedSubscript:@"Item"];

內(nèi)存管理陷阱
Objective-C的內(nèi)存管理機(jī)制是引用計(jì)數(shù)表悬,JavaScript的內(nèi)存管理機(jī)制是垃圾回收。在大部分情況下炉旷,JavaScriptCore能做到在這兩種內(nèi)存管理機(jī)制之間無(wú)縫無(wú)錯(cuò)轉(zhuǎn)換签孔,但也有少數(shù)情況需要特別注意。

在block內(nèi)捕獲JSContext
Block會(huì)為默認(rèn)為所有被它捕獲的對(duì)象創(chuàng)建一個(gè)強(qiáng)引用窘行。JSContext為它管理的所有JSValue也都擁有一個(gè)強(qiáng)引用饥追。并且,JSValue會(huì)為它保存的值和它所在的Context都維持一個(gè)強(qiáng)引用罐盔。這樣JSContext和JSValue看上去是循環(huán)引用的但绕,然而并不會(huì),垃圾回收機(jī)制會(huì)打破這個(gè)循環(huán)引用惶看。

看下面的例子:

self.context[@"getVersion"] = ^{
NSString *versionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];

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

JSContext *context = [JSContext currentContext]; // 這里不要用self.context
JSValue *version = [JSValue valueWithObject:versionString inContext:context];

return version;
};

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

JSManagedValue
當(dāng)把一個(gè)JavaScript值保存到一個(gè)本地實(shí)例變量上時(shí)纬黎,需要尤其注意內(nèi)存管理陷阱幅骄。 用實(shí)例變量保存一個(gè)JSValue非常容易引起循環(huán)引用。

看以下下例子本今,自定義一個(gè)UIAlertView拆座,當(dāng)點(diǎn)擊按鈕時(shí)調(diào)用一個(gè)JavaScript函數(shù):

#import <UIKit/UIKit.h>
#import <JavaScriptCore/JavaScriptCore.h>

@interface MyAlertView : UIAlertView

- (id)initWithTitle:(NSString *)title
        message:(NSString *)message
        success:(JSValue *)successHandler
        failure:(JSValue *)failureHandler
        context:(JSContext *)context;
@end

按照一般自定義AlertView的實(shí)現(xiàn)方法主巍,MyAlertView需要持有successHandler,failureHandler這兩個(gè)JSValue對(duì)象

向JavaScript環(huán)境注入一個(gè)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];
};

因?yàn)镴avaScript環(huán)境中都是“強(qiáng)引用”(相對(duì)Objective-C的概念來(lái)說(shuō))的挪凑,這時(shí)JSContext強(qiáng)引用了一個(gè)presentNativeAlert函數(shù)孕索,這個(gè)函數(shù)中又強(qiáng)引用了MyAlertView 等于說(shuō)JSContext強(qiáng)引用了MyAlertView,而MyAlertView為了持有兩個(gè)回調(diào)強(qiáng)引用了successHandler和failureHandler這兩個(gè)JSValue躏碳,這樣MyAlertView和JavaScript環(huán)境互相引用了搞旭。

所以蘋果提供了一個(gè)JSMagagedValue類來(lái)解決這個(gè)問(wèn)題。

看MyAlertView.m的正確實(shí)現(xiàn):

  #import "MyAlertView.h"

 @interface XorkAlertView() <UIAlertViewDelegate>
 @property (strong, nonatomic) JSContext *ctxt;
 @property (strong, nonatomic) JSMagagedValue *successHandler;
 @property (strong, nonatomic) JSMagagedValue *failureHandler;
 @end

@implementation MyAlertView

- (id)initWithTitle:(NSString *)title
        message:(NSString *)message
        success:(JSValue *)successHandler
        failure:(JSValue *)failureHandler
        context:(JSContext *)context {

 self = [super initWithTitle: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.virtualMachine addManagedReference:_successHandler withOwner:self];

    _failureHandler = [JSManagedValue managedValueWithValue:failureHandler];
    [context.virtualMachine addManagedReference:_failureHandler withOwner:self];
}
return self;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == self.cancelButtonIndex) {
    JSValue *function = [self.failureHandler value];
    [function callWithArguments:@[]];
} else {
    JSValue *function = [self.successHandler value];
    [function callWithArguments:@[]];
}

[self.ctxt.virtualMachine removeManagedReference:_failureHandler withOwner:self];
[self.ctxt.virtualMachine removeManagedReference:_successHandler withOwner:self];
}    

@end

分析上面例子菇绵,從外部傳入的JSValue對(duì)象在類內(nèi)部使用JSManagedValue來(lái)保存肄渗。

JSManagedValue本身是一個(gè)弱引用對(duì)象,需要調(diào)用JSVirtualMachine的addManagedReference:withOwner:把它添加到JSVirtualMachine對(duì)象中脸甘,確保使用過(guò)程中JSValue不會(huì)被釋放

當(dāng)用戶點(diǎn)擊AlertView上的按鈕時(shí)恳啥,根據(jù)用戶點(diǎn)擊哪一個(gè)按鈕,來(lái)執(zhí)行對(duì)應(yīng)的處理函數(shù)丹诀,這時(shí)AlertView也隨即被銷毀。 這時(shí)需要手動(dòng)調(diào)用removeManagedReference:withOwner:來(lái)移除JSManagedValue翁垂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铆遭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子沿猜,更是在濱河造成了極大的恐慌枚荣,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啼肩,死亡現(xiàn)場(chǎng)離奇詭異橄妆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)祈坠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門害碾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人赦拘,你說(shuō)我怎么就攤上這事慌随。” “怎么了躺同?”我有些...
    開(kāi)封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵阁猜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蹋艺,道長(zhǎng)剃袍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任捎谨,我火速辦了婚禮民效,結(jié)果婚禮上隘击,老公的妹妹穿的比我還像新娘。我一直安慰自己研铆,他們只是感情好埋同,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著棵红,像睡著了一般凶赁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逆甜,一...
    開(kāi)封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天虱肄,我揣著相機(jī)與錄音,去河邊找鬼交煞。 笑死咏窿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的素征。 我是一名探鬼主播集嵌,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼御毅!你這毒婦竟也來(lái)了根欧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤端蛆,失蹤者是張志新(化名)和其女友劉穎凤粗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體今豆,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫌拣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呆躲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片异逐。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歼秽,靈堂內(nèi)的尸體忽然破棺而出应役,到底是詐尸還是另有隱情,我是刑警寧澤燥筷,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布箩祥,位于F島的核電站,受9級(jí)特大地震影響肆氓,放射性物質(zhì)發(fā)生泄漏袍祖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一谢揪、第九天 我趴在偏房一處隱蔽的房頂上張望蕉陋。 院中可真熱鬧捐凭,春花似錦、人聲如沸凳鬓。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缩举。三九已至垦梆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仅孩,已是汗流浹背托猩。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辽慕,地道東北人京腥。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像溅蛉,于是被迫代替她去往敵國(guó)和親公浪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • OC與JS交互之JavaScriptCore 本文摘抄自:https://hjgitbook.gitbooks.i...
    大沖哥閱讀 1,022評(píng)論 0 1
  • 注:JavaScriptCore API也可以用Swift來(lái)調(diào)用温艇,本文用Objective-C來(lái)介紹因悲。 在iOS7...
    JW_T閱讀 555評(píng)論 0 0
  • 蘋果在iOS7推出的JavaScriptCore框架可以方便的完成OC和JS之間的交互,本篇文章主要研究一下Jav...
    OneAlon閱讀 456評(píng)論 0 0
  • 隨著H5技術(shù)的興起,在iOS開(kāi)發(fā)過(guò)程中勺爱,難免會(huì)遇到原生應(yīng)用需要和H5頁(yè)面交互的問(wèn)題。其中會(huì)涉及方法調(diào)用及參數(shù)傳值等...
    Chris_js閱讀 3,068評(píng)論 1 8
  • 原:https://github.com/YanlongMa/SwiftJavaScriptCore 注:Java...
    Tippi閱讀 371評(píng)論 0 1