對(duì)于 iOS Native 與 JS 交互我們先從調(diào)用方向上分為兩種情況來看:
JS 調(diào)用 Native
Native 調(diào)用 JS
- JS 調(diào)用 Native
其實(shí) JS 調(diào)用 iOS Native 也分為兩種實(shí)現(xiàn)方式:
a. 假 Request 方法
b. JavaScriptCore 方法
a. 假 Request 方法
原理:其實(shí)這種方式就是利用了 webview 的代理方法,在 webview 開始請(qǐng)求的時(shí)候截獲請(qǐng)求,判斷請(qǐng)求是否為約定好的假請(qǐng)求。如果是假請(qǐng)求則表示是 JS 想要按照約定調(diào)用我們的 Native 方法,按照約定去執(zhí)行我們的 Native 代碼就好。
① UIWebView
UIWebView 代理有用于截獲請(qǐng)求的函數(shù),在里面做判斷就好:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
// 與約定好的函數(shù)名作比較
if ([[url scheme] isEqualToString:@"your_func_name"]) {
// just do it
}
}
② WKWebView
WKWebView 有兩個(gè)代理,一個(gè)是 WKNavigationDelegate,另一個(gè)是 WKUIDelegate。WKUIDelegate 在另一篇文章中講到,這里我們需要設(shè)置并實(shí)現(xiàn)它的 WKNavigationDelegate 方法:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
// 與約定好的函數(shù)名作比較
if ([[url scheme] isEqualToString:@"your_func_name"]) {
// just do it
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
decisionHandler 是當(dāng)你的應(yīng)用程序決定是允許還是取消導(dǎo)航時(shí),要調(diào)用的代碼塊。 該代碼塊使用單個(gè)參數(shù),它必須是枚舉類型 WKNavigationActionPolicy 的常量之一。如果不調(diào)用 decisionHandler 會(huì)引起 crash。
這里補(bǔ)充一下 JS 代碼:
function callNative() {
loadURL("your_func_name://xxx");
}
然后用一下就好了
b. JavaScriptCore 方法
iOS 7 有了 JavaScriptCore 專門用來做 Native 與 JS 的交互。我們可以在 webview 完成加載之后獲取 JSContext,然后利用 JSContext 將 JS 中的對(duì)象引用過來用 Native 代碼對(duì)其作出解釋或響應(yīng):
// 首先引入 JavaScriptCore 庫
#import <JavaScriptCore/JavaScriptCore.h>
// 然后再 UIWebView 的完成加載的代理方法中
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 獲取 JS 上下文
jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 做引用,將 JS 內(nèi)的元素引用過來解釋,比如方法可以解釋成 Block,對(duì)象也可以指向 OC 的 Native 對(duì)象哦
jsContext[@"iosDelegate"] = self;
jsContext[@"yourFuncName"] = ^(id parameter){
// 注意這里的線程默認(rèn)是 web 處理的線程,如果涉及主線程操作需要手動(dòng)轉(zhuǎn)到主線程
dispatch_async(dispatch_get_main_queue(), ^{
// your code
});
}
}
而 JS 這邊代碼更簡單了,干脆聲明一個(gè)不解釋的函數(shù)(約定好名字的),用于給 Native 做引用:
var parameter = xxx;
yourFuncName(parameter);
- iOS Native 調(diào)用 JS
iOS Native 調(diào)用 JS 的實(shí)現(xiàn)方法也被 JavaScriptCore 劃分開來:
a. webview 直接注入 JS 并執(zhí)行
b. JavaScriptCore 方法
a. webview 直接注入 JS 并執(zhí)行
在 iOS 平臺(tái),webview 有注入并執(zhí)行 JS 的 API。
UIWebView
UIWebView 有直接注入 JS 的方法:
NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')", @"alert msg"];
[_webView stringByEvaluatingJavaScriptFromString:jsStr];
這個(gè)方法會(huì)返回運(yùn)行 JS 的結(jié)果(nullable NSString *),它是一個(gè)同步方法,會(huì)阻塞當(dāng)前線程!盡管此方法不被棄用,但最佳做法是使用 WKWebView 類的 evaluateJavaScript:completionHandler:method。
WKWebView
不同于 UIWebView,WKWebView 注入并執(zhí)行 JS 的方法不會(huì)阻塞當(dāng)前線程。因?yàn)榭紤]到 webview 加載的 web content 內(nèi) JS 代碼不一定經(jīng)過驗(yàn)證,如果阻塞線程可能會(huì)掛起 App。
NSString *jsStr = [NSString stringWithFormat:@"setLocation('%@')", @"北京市東城區(qū)南鑼鼓巷納福胡同xx號(hào)"];
[_webview evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@----%@", result, error);
}];
這個(gè)方法不會(huì)阻塞線程,而且它的回調(diào)代碼塊總是在主線程中運(yùn)行。
b. JavaScriptCore 方法
// 首先引入 JavaScriptCore 庫
#import <JavaScriptCore/JavaScriptCore.h>
// 先獲取 JS 上下文
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 如果涉及 UI 操作,切回主線程調(diào)用 JS 代碼中的 YourFuncName,通過數(shù)組@[parameter] 入?yún)?dispatch_async(dispatch_get_main_queue(), ^{
JSValue *jsValue = self.jsContext[@"YourFuncName"];
[jsValue callWithArguments:@[parameter]];
});
上面的代碼調(diào)用了 JS 代碼中 YourFuncName 函數(shù),并且給函數(shù)加了 @[parameter] 作為入?yún)?。這里再貼一下 JS 代碼:
function YourFuncName(arguments){
var result = arguments;
// do what u want to do
}