序言
在實(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-------");
};
}
注意:
- JS 要調(diào)用的原生 OC 方法,可以在viewDidLoad wevView 被創(chuàng)建后就添加好,但最好是在網(wǎng)址加載成功后再添加,以避免無(wú)法預(yù)料的亂入Bug。
- block 中的執(zhí)行環(huán)境是在子線(xiàn)程中。奇怪的是竟然可以更新部分UI,例如給view設(shè)置背景色,調(diào)用webView執(zhí)行js等,但是彈出原生alertView就會(huì)在控制臺(tái)報(bào)子線(xiàn)程操作UI的錯(cuò)誤信息。
- 避免循環(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é)果如下

如果我們要在 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?Core 和 JavaScriptCore框架在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
效果展示如下:

項(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