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

序言

在實(shí)際開(kāi)發(fā)中,我們避免不了需要和 UIWebView 打交道,這就涉及到 JS 與原生 OC 的交互,今天抽空總結(jié)一下 JS 與原生交互使用 JavaScriptCore 方式

JS 調(diào)用原生 OC

在iOS 7之后,apple添加了一個(gè)新的庫(kù)JavaScriptCore,用來(lái)做JS交互,因此JS與原生OC交互也變得簡(jiǎn)單了許多。

首先導(dǎo)入JavaScriptCore庫(kù), 然后在OC中獲取JS的上下文。

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

再然后定義好JS需要調(diào)用的方法,例如JS要調(diào)用share方法:
則可以在UIWebView加載url完成后,在其代理方法中添加要調(diào)用的share方法:

- (void)setupData {
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    //定義好JS要調(diào)用的方法, share就是調(diào)用的share方法名
    context[@"share"] = ^() {
        NSLog(@"+++++++Begin Log+++++++");
        NSArray *args = [JSContext currentArguments];
        NSMutableString *strM = [NSMutableString string];
        for (JSValue *jsVal in args) {
            NSLog(@"%@", jsVal.toString);
            [strM appendString:jsVal.toString];
        }
        
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"這是OC原生的彈出窗" message:strM delegate:self cancelButtonTitle:@"收到" otherButtonTitles:nil];
            [alertView show];
        });
        
        
        NSLog(@"-------End Log-------");
    };
}

注意:

  1. JS 要調(diào)用的原生 OC 方法,可以在viewDidLoad wevView 被創(chuàng)建后就添加好,但最好是在網(wǎng)址加載成功后再添加,以避免無(wú)法預(yù)料的亂入Bug。
  2. block 中的執(zhí)行環(huán)境是在子線(xiàn)程中。奇怪的是竟然可以更新部分UI,例如給view設(shè)置背景色,調(diào)用webView執(zhí)行js等,但是彈出原生alertView就會(huì)在控制臺(tái)報(bào)子線(xiàn)程操作UI的錯(cuò)誤信息。
  3. 避免循環(huán)引用,因?yàn)閎lock 會(huì)持有外部變量,而JSContext也會(huì)強(qiáng)引用它所有的變量,因此在block中調(diào)用self時(shí),要用__weak 轉(zhuǎn)一下。而且在block內(nèi)不要使用外部的context 以及JSValue,都會(huì)導(dǎo)致循環(huán)引用。如果要使用context 可以使用[JSContext currentContext]。當(dāng)然我們可以將JSContext 和JSValue當(dāng)做block的參數(shù)傳進(jìn)去,這樣就可以使用啦。

其中相對(duì)應(yīng)的 HTML 代碼如下:

<html>
    <header>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <script type="text/javascript">
        
            function secondClick() {
                share('分享的標(biāo)題','分享的內(nèi)容','圖片地址');
            }
        
        function showAlert(message){
            alert(message);
        }
        
        </script>
    </header>
    
    <body>
        <h2> 這里是第二種方式 </h2>
        <br/>
        <br/>
        <button type="button" onclick="secondClick()">Click Me!</button>
        
    </body>
</html>

由此可見(jiàn),JS 部分確實(shí)簡(jiǎn)單多了。

OC 調(diào)用 JS

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

  • 方式一

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

// 法一
- (void)transferJS {
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    NSString *textJS = @"showAlert('這里是JS中alert彈出的message')";
    [context evaluateScript:textJS];
}

// 法二
- (void)transferJS {
    NSString *textJS = @"showAlert('這里是JS中alert彈出的message')";
    [[JSContext currentContext] evaluateScript:textJS];
}
  • 方式二
    使用 JSValue 的方法-callWithArguments,也可以實(shí)現(xiàn) OC 調(diào)用 JS 方法
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context[@"showAlert"] callWithArguments:@[@"這里是JS中alert彈出的message"]];

重點(diǎn):
stringByEvaluatingJavaScriptFromString是一個(gè)同步的方法,使用它執(zhí)行JS方法時(shí),如果JS 方法比較耗的時(shí)候,會(huì)造成界面卡頓。尤其是js 彈出alert 的時(shí)候。
alert 也會(huì)阻塞界面,等待用戶(hù)響應(yīng),而stringByEvaluatingJavaScriptFromString又會(huì)等待js執(zhí)行完畢返回。這就造成了死鎖。
官方推薦使用WKWebView的evaluateJavaScript:completionHandler:代替這個(gè)方法。
其實(shí)我們也有另外一種方式,自定義一個(gè)延遲執(zhí)行alert 的方法來(lái)防止阻塞,然后我們調(diào)用自定義的alert 方法。同理,耗時(shí)較長(zhǎng)的js 方法也可以放到setTimeout 中。

function asyncAlert(content) {
    setTimeout(function(){
         alert(content);
         },1);
}
補(bǔ)充
  • 好處:使用JavaScriptCore,JS調(diào)用Native方法時(shí),參數(shù)的傳遞更方便,不用擔(dān)心特殊符號(hào)的轉(zhuǎn)換問(wèn)題。

  • 不好的地方:只能使用在iOS 7以上。

  • 擴(kuò)展
    在 OC 中如何往 JS 環(huán)境添加一個(gè)變量,便于后續(xù)在 JS 中調(diào)用?

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 中的按鈕

<input type="button" value="輸出arr" onclick="showArr()" />
function showArr(){
     asyncAlert(arr);
}
            
function asyncAlert(content) {
     setTimeout(function(){
               alert(content);
         },1);
}

直接輸出 arr,結(jié)果如下


show.png

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

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

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

關(guān)于JSExport如何使用?
JSExport 主要是用于將OC中定義的Model類(lèi)等引入到JavaScript中,便于在JS中使用這種對(duì)象和對(duì)象的屬性、方法。
JSExport的大致使用流程是:
1.創(chuàng)建一個(gè)自定義協(xié)議XXXExport 繼承自JSExport。
2.在自定義的XXXExport中添加JS里需要調(diào)用的屬性和方法。
3.在自定義的Model類(lèi)中實(shí)現(xiàn)XXXExport中的屬性的get/set方法以及定義的方法。
4.通過(guò)JSContext將Model類(lèi)或者M(jìn)odel類(lèi)的實(shí)例插入到JavaScript中。

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

WKWebView 與JavaScriptCore

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

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

那么就不能在WKWebView中使用JavaScriptCore了。
而且,WKWebView中有OC 和JS交互的方式,更easy 、更簡(jiǎn)潔,因此也用不著使用JavaScriptCore。

WKWebView 中 OC 與 JS 交互可以使用 MessageHandler。詳情請(qǐng)看下文
iOS下 JS 與 OC 互相調(diào)用(四)-MessageHandler

效果展示如下:

js_oc_javaScriptCore.gif

項(xiàng)目連接地址-JS_OC_InterceptUrl


本文參考iOS下JS與OC互相調(diào)用(四)--JavaScriptCore,非常感謝該作者。


更多 JS 與 OC 交互文章請(qǐng)看下面
iOS下 JS 與OC 互相調(diào)用(一) - UIWebView 攔截 URL
iOS 下 JS 與 OC 互相調(diào)用(三) - WKWebView 攔截 URL
iOS下JS與OC互相調(diào)用(四)-MessageHandler
iOS下 JS 與 OC 互相調(diào)用(五) - UIWebView+WebViewJavascriptBridge
iOS下 JS 與 OC 互相調(diào)用(六) - WKWebView+WKWebViewJavascriptBridge

生活源于創(chuàng)造,技術(shù)源于分享。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容