?一、簡(jiǎn)述
目前原生與JS交互的方式有以下幾種
JavaScriptCore
WKWebView
攔截URL
WebViewJavascriptBridge庫(kù)
二、JavaScriptCore
(一)定義
1.JSContext
為JS的執(zhí)行提供了上下文環(huán)境,通過(guò)JSCore執(zhí)行的JS代碼都要通過(guò)JSContext來(lái)執(zhí)行。(上下文對(duì)象給兩者的交互搭建了環(huán)境)
2.JSValue
是JS值在OC中的封裝徐鹤,以便JS值在OC中執(zhí)行
3.兩者關(guān)系
JSValue不可獨(dú)立存在配喳,必須依存于JSContext。一個(gè)JSContext可對(duì)應(yīng)多個(gè)JSValue
每一個(gè)JSValue對(duì)象都要強(qiáng)引用關(guān)聯(lián)一個(gè)JSContext凳干。當(dāng)與某JSContext對(duì)象關(guān)聯(lián)的所有JSValue釋放后晴裹,JSContext也會(huì)被釋放。?
(二)建立連接
1.文件頭部引入相關(guān)庫(kù)
#import <JavaScriptCore/JavaScriptCore.h>
2.加載本地HTML頁(yè)面
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"JSInteraction" ofType:@"html"];
??? NSString *htmlStr = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
??? [_webView loadHTMLString:htmlStr baseURL:[NSURL URLWithString:filePath]];
3.在webView的代理方法webViewDidFinishLoad中實(shí)現(xiàn)連接
//通過(guò)KVC方式從webView上獲取相應(yīng)的JSContext
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; ? ? ? ? ? ??
//設(shè)置錯(cuò)誤處理函數(shù)
[self.context setExceptionHandler:^(JSContext *context, JSValue *exception) {
NSLog(@"oc catches the exception: %@",exception); }];
self.context[@"JsInteractive"] = self;
Pay:由于加載JS在webViewDidFinishLoad()之前救赐,故JS無(wú)法調(diào)用OC方法涧团。
解決方法:
1)可在JS中構(gòu)建一個(gè)事件 “webViewDidFinishLoad()”,在原生的webVIewDidFinishLoad()方法中通過(guò)context的evaluateScript方法或者webView的stringByEvaluatingJavaScriptFromString()方法來(lái)調(diào)用经磅。
[self.context evaluateScript:@"webViewDidFinishLoad()"];
[webView stringByEvaluatingJavaScriptFromString:@"webViewDidFinishLoad()"];
2)通過(guò)JS延時(shí)操作setTimeout使得該方法在OC加載完畢后再執(zhí)行
setTimeout("showMessage('參數(shù)在此')",1000)
(三)交互方式
1.Block
1)JS調(diào)用OC
self.context[@"showMessage"] = ^(NSString *message){
?? NSLog(@"----showMessage-JS調(diào)用OC----參數(shù)為:%@",message); };
<button onclick="showMessage('我是參數(shù)')">showMessage-JS調(diào)用OC</button>
2)OC調(diào)用JS
?? //1.調(diào)用JS語(yǔ)句
? ? [self.context evaluateScript:@"alert('你好泌绣!');"];
? ? //2.調(diào)用JS中的變量
? ? JSValue *myValue = self.context[@"myObject"];
? ? NSLog(@"---%@---",[myValue toString]);
? ? //3.調(diào)用JS中的方法(以下兩種均可)
? ? [self.context evaluateScript:@"OcCallJs()"];
? ? [webView stringByEvaluatingJavaScriptFromString:@"OcCallJs()"];
? ? //調(diào)用有參數(shù)的方法
? ?? JSValue *news = self.context[@"showNews"];
?? ? [news callWithArguments:@[@"新聞?lì)^條",@"時(shí)事政治"]];
<script>
var myObject = "我的項(xiàng)目哈哈哈";
? ? ? ? ? ? function OcCallJs(){
? ? ? ? ? ? ? ? alert("我被OC調(diào)用了");
? ? ? ? ? ? }
??????????? function showNews(news,news2){
?????????????????? alert(news+news2);
?????? ? ?? }</script>
2.Delegate交互
當(dāng)OC方法為多參數(shù)函數(shù)時(shí),其在JS中調(diào)用需要更改一下以適應(yīng)JS語(yǔ)法:
1)移除所有冒號(hào)
2)跟在冒號(hào)后面的參數(shù)的第一個(gè)字母大寫(xiě)
@protocol JSInteraction
-(void)showDelegateMessage:(NSString *)message;
-(void)showDelegateMessage:(NSString *)message andNumber:(NSInteger)number;
@end
-(void)showDelegateMessage:(NSString *)message{
? ? NSLog(@"我是被JS調(diào)用的OC的delegate方法预厌,參數(shù)為%@",message);
}
-(void)showDelegateMessage:(NSString *)message andNumber:(NSInteger)number{
NSLog(@"delegate多參數(shù)阿迈,參數(shù)為%ld--%@",number,message);
}
<script>
function webViewDidFinish(){
? ? ? ? ? ? ? ? JsInteractive.showDelegateMessage('參數(shù)');
???????????????? JsInteractive.showDelegateMessageAndNumber('123','1000000');
?}
</script>
OcCallJs()方法在上文提到的webVIewDidFinishLoad()中調(diào)用
三、WKWebView
(一)定義
1.WKWebView
兩種創(chuàng)建方式
//普通初始化
? ? self.wkWebView = [[WKWebView alloc]init];
? ? self.wkWebView.frame = self.view.bounds;
? ? //config方式初始化
? ? WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
? ? self.wkWebView = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:config];
? ? self.wkWebView.navigationDelegate = self;
? ? self.wkWebView.UIDelegate = self;
? ? [self.view addSubview:self.wkWebView];
加載網(wǎng)頁(yè)的方式
//加載本地URL文件
- (nullableWKNavigation*)loadFileURL:(NSURL*)URL allowingReadAccessToURL:(NSURL*)readAccessURL
//加載本地HTML字符串
- (nullableWKNavigation*)loadHTMLString:(NSString*)string baseURL:(nullableNSURL*)baseURL;
//加載二進(jìn)制數(shù)據(jù)
- (nullableWKNavigation*)loadData:(NSData*)data MIMEType:(NSString*)MIMEType characterEncodingName:(NSString*)characterEncodingName baseURL:(NSURL*)baseURL
2.WKWebViewConfiguration
如上代碼轧叽,webView初始化時(shí)配置webView的屬性苗沧,JS調(diào)用原生時(shí)需要用到。
3.WKUserContentController
這個(gè)類(lèi)主要用來(lái)負(fù)責(zé)原生與JS的交互管理
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
- (void)removeScriptMessageHandlerForName:(NSString *)name;
addScriptMessageHandler:name:方法注冊(cè)要被JS調(diào)用的方法的名稱(chēng)炭晒,然后在對(duì)應(yīng)的JavaScript中使用
window.webkit.messageHandlers.<name>.postMessage(<data>)
方法來(lái)向native發(fā)送消息待逞。最后記得將方法名remove掉,否則會(huì)造成內(nèi)存泄漏网严。
4.WKScriptMessageHandler
用來(lái)處理JS調(diào)用原生的方法
//接受JS發(fā)給原生的消息
- (void)userContentController:(WKUserContentController *)userContentControllerdidReceiveScriptMessage:(WKScriptMessage *)message;
5.WKUIDelegate
協(xié)議主要用于WKWebView處理web界面的三種提示框(警告框识樱、確認(rèn)框、輸入框)震束。提供用原生控件顯示網(wǎng)頁(yè)的方法回調(diào)怜庸。
(二)建立連接
1.文件頭部引入相關(guān)庫(kù)
#import <WebKit/WebKit.h>
2.加載HTML頁(yè)面
NSURL *urlString = [[NSBundle mainBundle] URLForResource:@"JSInteraction.html" withExtension:nil];
? ? if (urlString) {
? ? ? ? [_wkWebView loadRequest:[NSURLRequest requestWithURL:urlString]];
? ? }
WKUserContentController *userCC = config.userContentController;
(三)交互方式
1.原生調(diào)用JS
WKWebView 提供了一個(gè)新的方法evaluateJavaScript:completionHandler:,實(shí)現(xiàn)OC 調(diào)用JS 等場(chǎng)景垢村。
//獲取到input標(biāo)簽的value屬性值
NSString *inputValue = @"document.getElementsByName('input')[0].attributes['value'].value";
???
??? /** native 調(diào)用JS的方法 */
??? [webView evaluateJavaScript:inputValue completionHandler:^(id _Nullable response, NSError * _Nullable error) {
??????? NSLog(@"value:%@,error:%@",response,error);
??? }];
2.JS調(diào)用原生
/** JS調(diào)用OC? **/
??? //此處相當(dāng)于監(jiān)聽(tīng)了JS中showMobile這個(gè)方法
??? [self.userCC addScriptMessageHandler:self name:@"showMobile"];
??? [self.userCC addScriptMessageHandler:self name:@"showName"];//移除WKUserContentController割疾,防止內(nèi)存泄漏
-(void)removeAllScriptHandler{
??? [self.userCC removeScriptMessageHandlerForName:@"showMobile"];
??? [self.userCC removeScriptMessageHandlerForName:@"showName"];
??? [self.userCC removeScriptMessageHandlerForName:@"showTwo"];
}
JS中:
?????? //多參數(shù)用數(shù)組傳遞
??????? function btnClick1() {??????????? window.webkit.messageHandlers.showMobile.postMessage(null)??????? }
??????? function btnClick2() {
??????????? window.webkit.messageHandlers.showName.postMessage('有個(gè)參數(shù)')
??????? }
??????? function? btnClick3() {
??????????? window.webkit.messageHandlers.showTwo.postMessage(['有兩個(gè)參數(shù)啦','canshu'])
??????? }
三、攔截URL
(一)定義
我們要攔截URL肝断,就要通過(guò)navigationDelegate的一個(gè)代理方法來(lái)實(shí)現(xiàn)杈曲。如果在HTML中要使用alert等彈窗,就必須得實(shí)現(xiàn)UIDelegate的相應(yīng)代理方法胸懈。
(二)建立連接
同WKWebView。
(三)交互方式
1.JS調(diào)用OC
2.OC 調(diào)用 JS
JS 調(diào)用OC 方法后恰响,有的操作可能需要將結(jié)果返回給JS趣钱。這時(shí)候就是OC 調(diào)用JS 方法的場(chǎng)景。
WKWebView 提供了一個(gè)新的方法evaluateJavaScript:completionHandler:胚宦,實(shí)現(xiàn)OC 調(diào)用JS 等場(chǎng)景首有。
使用自定義loadURL的原因:
如果當(dāng)前網(wǎng)頁(yè)正使用window.location.href加載網(wǎng)頁(yè)的同時(shí)燕垃,調(diào)用window.location.href去調(diào)用OC原生方法,會(huì)導(dǎo)致加載網(wǎng)頁(yè)的操作被取消掉井联。同樣的卜壕,如果連續(xù)使用window.location.href執(zhí)行兩次OC原生調(diào)用,也有可能導(dǎo)致第一次的操作被取消掉烙常。
自定義asyncAlert的原因
因?yàn)橛械腏S調(diào)用是需要OC 返回結(jié)果到JS的轴捎。stringByEvaluatingJavaScriptFromString是一個(gè)同步方法,會(huì)等待js 方法執(zhí)行完成蚕脏,而彈出的alert 也會(huì)阻塞界面等待用戶(hù)響應(yīng)侦副,所以他們可能會(huì)造成死鎖。導(dǎo)致alert 卡死界面驼鞭。如果回調(diào)的JS 是一個(gè)耗時(shí)的操作秦驯,那么建議將耗時(shí)的操作也放入setTimeout的function中。
3.同步和異步
因?yàn)?iOS SDK 沒(méi)有天生支持 js 和 native
相互調(diào)用挣棕,大家的技術(shù)方案都是自己實(shí)現(xiàn)的一套調(diào)用機(jī)制译隘,所以這里面有同步異步的問(wèn)題。細(xì)心的同學(xué)就能發(fā)現(xiàn)洛心,js 調(diào)用 native 是通過(guò)插入一個(gè)
iframe细燎,這個(gè) iframe 插入完了就完了,執(zhí)行的結(jié)果需要 native 另外用
stringByEvaluatingJavaScriptFromString 方法通知 js皂甘,所以這是一個(gè)異步的調(diào)用玻驻。
而 stringByEvaluatingJavaScriptFromString 方法本身會(huì)直接返回一個(gè) NSString 類(lèi)型的執(zhí)行結(jié)果,所以這顯然是一個(gè)同步調(diào)用偿枕。
所以 js call native 是異步璧瞬,native call js 是同步。
四渐夸、WebViewJavascriptBridge
1.實(shí)質(zhì):攔截URL來(lái)實(shí)現(xiàn)的調(diào)用原生功能
2.JS中實(shí)現(xiàn)的方法
function setupWebViewJavascriptBridge(callback) {
??????????????? if (window.WebViewJavascriptBridge) {
??????????????????? return callback(WebViewJavascriptBridge);
??????????????? }
??????????????? if (window.WVJBCallbacks) {
??????????????????? return window.WVJBCallbacks.push(callback);
??????????????? }
??????????????? window.WVJBCallbacks = [callback];
??????????????? var WVJBIframe = document.createElement('iframe');
??????????????? WVJBIframe.style.display = 'none';
??????????????? WVJBIframe.src = 'https://__bridge_loaded__';
??????????????? document.documentElement.appendChild(WVJBIframe);
??????????????? setTimeout(function() {
??????????????????? document.documentElement.removeChild(WVJBIframe)
???????????????????? }, 0)
??????????? }
這個(gè)方法的作用主要是在第一次加載HTML的時(shí)候起作用嗤锉,目的是加載一次wvjbscheme://__BRIDGE_LOADED__,來(lái)觸發(fā)往HTML中注入一些已經(jīng)寫(xiě)好的JS方法墓塌。
setupWebViewJavascriptBridge(function(bridge) {
?????????????? /* Initialize your app here */
?????????????? bridge.registerHandler('JS Echo', function(data, responseCallback) {
??????????????????????????????????????????????????????????? console.log("JS Echo called with:", data)
??????????????????????????????????????????????????????????? responseCallback(data)
??????????????????????????????????????????????????????????? })
??????????????? bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
??????????????????????????? console.log("JS received response:", responseData)
??????????????????????????????????????????????????????? })
??????????????????? })
Native 需要調(diào)用的 JS 功能瘟忱,也是需要先注冊(cè),然后再執(zhí)行的苫幢。如果Native 需要調(diào)用的JS 功能有多個(gè)访诱,那么這些功能都要在這里先注冊(cè),注冊(cè)之后才能夠被Native 調(diào)用韩肝。
參照:
http://www.reibang.com/p/7151987f012d
http://blog.devtang.com/2012/03/24/talk-about-uiwebview-and-phonegap/