iOS下JS與OC互相調(diào)用(四)--JavaScriptCore

前面講完攔截URL的方式實(shí)現(xiàn)JS與OC互相調(diào)用,終于到JavaScriptCore了。它是從iOS7開始加入的,用 Objective-C 把 WebKit 的 JavaScript 引擎封裝了一下,提供了簡單快捷的方式與JavaScript交互。?

關(guān)于JavaScriptCore的使用有兩篇很好的文章:?

NSHipster中文版的Java?Script?Core?

iOS7 新JavaScriptCore框架入門介紹

看了上述兩篇文章,對JavaScriptCore應(yīng)該已經(jīng)基本了解了。我就簡要介紹一下,然后用代碼來實(shí)際操作了。先上最終實(shí)現(xiàn)的效果:

1、簡要介紹JavaScriptCore

JavaScriptCore是一個iOS 7 新添加的框架,使用前需要先導(dǎo)入JavaScriptCore.framework。?

然后我們在JavaScriptCore.h中可以看到,該框架主要的類就只有五個:

* 1.1 JSVirtualMachine *?

JSVirtualMachine看名字直譯是JS 虛擬機(jī),也就是說JavaScript是在一個虛擬的環(huán)境中執(zhí)行,而JSVirtualMachine為其執(zhí)行提供底層資源。

翻譯這段描述:一個JSVirtualMachine實(shí)例,代表一個獨(dú)立的JavaScript對象空間,并為其執(zhí)行提供資源。它通過加鎖虛擬機(jī),保證JSVirtualMachine是線程安全的,如果要并發(fā)執(zhí)行JavaScript,那我們必須創(chuàng)建多個獨(dú)立的JSVirtualMachine實(shí)例,在不同的實(shí)例中執(zhí)行JavaScript。

通過alloc/init就可以創(chuàng)建一個新的JSVirtualMachine對象。但是我們一般不用新建JSVirtualMachine對象,因為創(chuàng)建JSContext時,如果我們不提供一個特性的JSVirtualMachine,內(nèi)部會自動創(chuàng)建一個JSVirtualMachine對象。

* 1.2 JSContext *?

JSContext是為JavaScript的執(zhí)行提供運(yùn)行環(huán)境,所有的JavaScript的執(zhí)行都必須在JSContext環(huán)境中。JSContext也管理JSVirtualMachine中對象的生命周期。每一個JSValue對象都要強(qiáng)引用關(guān)聯(lián)一個JSContext。當(dāng)與某JSContext對象關(guān)聯(lián)的所有JSValue釋放后,JSContext也會被釋放。?

創(chuàng)建一個JSContext對象的方式有:

// 1.這種方式需要傳入一個JSVirtualMachine對象,如果傳nil,會導(dǎo)致應(yīng)用崩潰的。JSVirtualMachine *JSVM = [[JSVirtualMachine alloc] init];

JSContext *JSCtx = [[JSContext alloc] initWithVirtualMachine:JSVM];// 2.這種方式,內(nèi)部會自動創(chuàng)建一個JSVirtualMachine對象,可以通過JSCtx.virtualMachine// 看其是否創(chuàng)建了一個JSVirtualMachine對象。JSContext *JSCtx = [[JSContext alloc] init];// 3. 通過webView的獲取JSContext。JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

上面推薦的兩篇文章以及網(wǎng)上介紹JavaScriptCore的文章多是通過1和2這兩種方式創(chuàng)建JSContext,然后執(zhí)行JavaScript,演示JavaScriptCore。我一直有疑問,如果不是HTML結(jié)合OC,才會使用到JavaScript,那在一個虛擬的環(huán)境里運(yùn)行JS有上面意義。?

所以,后面我是用方式3來創(chuàng)建JSContext。

* 1.3 JSValue *?

JSValue都是通過JSContext返回或者創(chuàng)建的,并沒有構(gòu)造方法。JSValue包含了每一個JavaScript類型的值,通過JSValue可以將Objective-C中的類型轉(zhuǎn)換為JavaScript中的類型,也可以將JavaScript中的類型轉(zhuǎn)換為Objective-C中的類型。?

上述兩篇文章中均有OC、JSValue、JavaScript的類型對應(yīng)關(guān)系表。?

* 1.4 JSManagedValue *?

JSManagedValue主要用途是解決JSValue對象在Objective-C 堆上的安全引用問題。把JSValue 保存進(jìn)Objective-C 堆對象中是不正確的,這很容易引發(fā)循環(huán)引用,而導(dǎo)致JSContext不能釋放。?

這個類主要是將JSValue對象轉(zhuǎn)換為JSManagedValue的API,而且也不常用,就不做具體介紹了。以后遇到使用場景再補(bǔ)充。

* 1.5 JSExport *?

JSExport是一個協(xié)議類,但是該協(xié)議并沒有任何屬性和方法。?

怎么使用呢??

我們可以自定義一個協(xié)議類,繼承自JSExport。無論我們在JSExport里聲明的屬性,實(shí)例方法還是類方法,繼承的協(xié)議都會自動的提供給任何 JavaScript 代碼。?

So,我們只需要在自定義的協(xié)議類中,添加上屬性和方法就可以了。

2、代碼操作展示

因為該系列主要是JS與OC互調(diào),所以主要介紹如何用JavaScriptCore實(shí)現(xiàn)JS與OC互調(diào)。

2.1 創(chuàng)建UIWebView,并加載本地HTML。

這步跟?文章(一)中的步驟一是一樣的。

self.webView = [[UIWebView alloc] initWithFrame:self.view.frame];

? ? self.webView.delegate = self;

? ? NSURL *htmlURL = [[NSBundle mainBundle] URLForResource:@"index.html" withExtension:nil];//? ? NSURL *htmlURL = [NSURL URLWithString:@"http://www.baidu.com"];? ? NSURLRequest *request = [NSURLRequest requestWithURL:htmlURL];

? ? // 如果不想要webView 的回彈效果? ? self.webView.scrollView.bounces = NO;

? ? // UIWebView 滾動的比較慢,這里設(shè)置為正常速度? ? self.webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;

? ? [self.webView loadRequest:request];

? ? [self.view addSubview:self.webView];

HTML的內(nèi)容也大致一樣,不過JS的調(diào)用有些區(qū)別,更簡單了。

functionshareClick(){ share('測試分享的標(biāo)題','測試分享的內(nèi)容','url=http://www.baidu.com');

}functionshareResult(channel_id,share_channel,share_url){? ? var content = channel_id+","+share_channel+","+share_url;

? ? asyncAlert(content);

? ? document.getElementById("returnValue").value = content;

}functionlocationClick(){? ? getLocation();

}functionsetLocation(location){? ? asyncAlert(location);

? ? document.getElementById("returnValue").value = location;

}

更詳細(xì)的可以看demo中的HTML源碼,demo地址在文章末。

2.2 添加JS要調(diào)用的原生OC方法

在HMTL加載成功的回調(diào)方法- (void)webViewDidFinishLoad:(UIWebView *)webView中添加要調(diào)用的原生OC方法。

#pragma mark - UIWebViewDelegate- (void)webViewDidFinishLoad:(UIWebView *)webView

{

? ? NSLog(@"webViewDidFinishLoad");

? ? [self addCustomActions];

}

將所有要添加的功能方法,集中到一個方法addCustomActions中,便于維護(hù)。

#pragma mark - private method-(void)addCustomActions{

? ? JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

? ? [self addScanWithContext:context];

? ? [self addLocationWithContext:context];

? ? [self addSetBGColorWithContext:context];

? ? [self addShareWithContext:context];

? ? [self addPayActionWithContext:context];

? ? [self addShakeActionWithContext:context];

? ? [self addGoBackWithContext:context];

}

然后每一個小功能獨(dú)立開來,這樣修改和解決Bug的時候能夠快速定位到某個功能。

- (void)addShareWithContext:(JSContext *)context

{

? ? __weak typeof(self) weakSelf = self;

? ? context[@"share"] = ^() {

? ? ? ? NSArray *args = [JSContext currentArguments];

? ? ? ? if (args.count < 3) {

? ? ? ? ? ? return ;

? ? ? ? }

? ? ? ? NSString *title = [args[0] toString];

? ? ? ? NSString *content = [args[1] toString];

? ? ? ? NSString *url = [args[2] toString];

? ? ? ? // 在這里執(zhí)行分享的操作...? ? ? ? // 將分享結(jié)果返回給js? ? ? ? NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];

? ? ? ? [[JSContext currentContext] evaluateScript:jsStr];

? ? };

}

注意:?

* 1.JS要調(diào)用的原生OC方法,可以在viewDidLoad webView被創(chuàng)建后就添加好,但最好是在網(wǎng)址加載成功后再添加,以避免無法預(yù)料的亂入Bug。?

* 2.block 中的執(zhí)行環(huán)境是在子線程中。奇怪的是竟然可以更新部分UI,例如給view設(shè)置背景色,調(diào)用webView執(zhí)行js等,但是彈出原生alertView就會在控制臺報子線程操作UI的錯誤信息。?

* 3.避免循環(huán)引用,因為block 會持有外部變量,而JSContext也會強(qiáng)引用它所有的變量,因此在block中調(diào)用self時,要用__weak 轉(zhuǎn)一下。而且在block內(nèi)不要使用外部的context 以及JSValue,都會導(dǎo)致循環(huán)引用。如果要使用context 可以使用[JSContext currentContext]。當(dāng)然我們可以將JSContext 和JSValue當(dāng)做block的參數(shù)傳進(jìn)去,這樣就可以使用啦。

2.3 OC調(diào)用JS方法

OC調(diào)用JS方法就有多種方式了。首先介紹使用JavaScriptCore框架的方式。?

* 方式1 *?

使用JSContext的方法-evaluateScript,可以實(shí)現(xiàn)OC調(diào)用JS方法。?

下面是一個調(diào)用JS中payResult方法的示例代碼:

NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];

[[JSContext currentContext] evaluateScript:jsStr];

* 方式2 *?

使用JSValue的方法-callWithArguments,也可以實(shí)現(xiàn)OC調(diào)用JS方法。?

下面這個示例代碼依然是調(diào)用JS中的payResult:

JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

[context[@"payResult"] callWithArguments:@[@"支付彈窗"]];

當(dāng)然,如果是在執(zhí)行原生OC方法之后,想要在OC執(zhí)行完操作后,將結(jié)果回調(diào)給JS時,可以這樣寫:

- (void)addPayActionWithContext:(JSContext *)context

{

? ? context[@"payAction"] = ^() {

? ? ? ? NSArray *args = [JSContext currentArguments];

? ? ? ? if (args.count < 4) {

? ? ? ? ? ? return ;

? ? ? ? }

? ? ? ? NSString *orderNo = [args[0] toString];

? ? ? ? NSString *channel = [args[1] toString];

? ? ? ? long long amount = [[args[2] toNumber] longLongValue];

? ? ? ? NSString *subject = [args[3] toString];

? ? ? ? // 支付操作? ? ? ? NSLog(@"orderNo:%@---channel:%@---amount:%lld---subject:%@",orderNo,channel,amount,subject);

? ? ? ? // 將支付結(jié)果返回給js? ? ? ? [[JSContext currentContext][@"payResult"] callWithArguments:@[@"支付成功"]];

? ? };

}

* 方式3 *以前介紹過的,利用UIWebView的API。

NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];

[weakSelf.webView stringByEvaluatingJavaScriptFromString:jsStr];

3、補(bǔ)充介紹JavaScriptCore

好處:使用JavaScriptCore,JS調(diào)用Native方法時,參數(shù)的傳遞更方便,不用擔(dān)心特殊符號的轉(zhuǎn)換問題。?

不好的地方:只能使用在iOS 7以上。這點(diǎn)我相信現(xiàn)在基本沒有多少應(yīng)用還兼容iOS 6了吧,我去年在做這個功能的時候,還要兼容iOS 6 ?? ?? 。

先把JS與OC互調(diào)部分的介紹完了,這里再補(bǔ)充一些關(guān)于JavaScriptCore的相關(guān)知識。?

在OC中如何往JS環(huán)境中添加一個變量,便于后續(xù)在JS中使用呢?

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

[context evaluateScript:@"var arr = [3, 4, 'abc'];"];

而用到實(shí)際的UIWebView上,可以這樣:

JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

[context evaluateScript:@"var arr = [3, 4, 'abc'];"];

當(dāng)上面這兩行代碼執(zhí)行完后,我點(diǎn)擊HTML中的按鈕

functionshowArr(){

? ? asyncAlert(arr);

}functionasyncAlert(content) {

? ? setTimeout(function(){

? ? ? ? ? ? ? alert(content);

? ? ? ? },1);

}

直接輸出arr,結(jié)果是這樣的:

如果我們在OC中想要取出arr,只需要這樣:

JSValue *value = context[@"arr"];

OC中的block可以傳入到JavaScript中,這樣就創(chuàng)建了一個新的JS方法。我們上面的JS調(diào)用OC方法,就是利用的這個實(shí)現(xiàn)的。

關(guān)于JSExport如何使用??

JSExport 主要是用于將OC中定義的Model類等引入到JavaScript中,便于在JS中使用這種對象和對象的屬性、方法。?

JSExport的大致使用流程是:?

1.創(chuàng)建一個自定義協(xié)議XXXExport?繼承自JSExport。?

2.在自定義的XXXExport中添加JS里需要調(diào)用的屬性和方法。?

3.在自定義的Model類中實(shí)現(xiàn)XXXExport中的屬性的get/set方法以及定義的方法。?

4.通過JSContext將Model類或者M(jìn)odel類的實(shí)例插入到JavaScript中。

當(dāng)然,我們也可以給已經(jīng)存在的類動態(tài)添加協(xié)議,來使其可以供JS 使用。這些示例和示例代碼,在文章NSHipster中文版的Java?Script?Core?和?JavaScriptCore框架在iOS7中的對象交互和管理中有很詳細(xì)的介紹和使用展示。

WKWebView 與JavaScriptCore

關(guān)于WKWebView 與JavaScriptCore,由于WKWebView 不支持通過如下的KVC的方式創(chuàng)建JSContext:

JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

那么就不能在WKWebView中使用JavaScriptCore了。?

而且,WKWebView中有OC 和JS交互的方式,更easy 、更簡潔,因此也用不著使用JavaScriptCore。?

WKWebView中如何實(shí)現(xiàn)OC與JS交互可以看前面這篇文章:iOS下JS與OC互相調(diào)用(三)–MessageHandler

UIWebView利用JavaScriptCore來實(shí)現(xiàn)交互的示例工程:JS_OC_JavaScriptCore

Have Fun!

版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請注明出處。 https://blog.csdn.net/u011619283/article/details/52311143

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評論 2 380

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

  • 隨著H5技術(shù)的興起,在iOS開發(fā)過程中,難免會遇到原生應(yīng)用需要和H5頁面交互的問題。其中會涉及方法調(diào)用及參數(shù)傳值等...
    Chris_js閱讀 3,109評論 1 8
  • OC與JS交互之JavaScriptCore 本文摘抄自:https://hjgitbook.gitbooks.i...
    大沖哥閱讀 1,028評論 0 1
  • 本博客主要分以下幾個方面來介紹iOS中的JavaScriptCore JavaScriptCore簡介 JavaS...
    dullgrass閱讀 4,290評論 1 38
  • 前言 Web 頁面中的 JS 與 iOS Native 如何交互是每個 iOS 猿必須掌握的技能。而說到 Nati...
    幽城88閱讀 2,224評論 1 8
  • 前不久一口氣買了八九本書也計劃著每周一點(diǎn)一點(diǎn)看完還有兩三本吧 已經(jīng)迫不及待又想買新書了湊單的時候反復(fù)糾結(jié)最后選了這...
    Mangobb閱讀 486評論 0 1