Swift JavaScript与WKWebView的同步本机通信

Swift JavaScript与WKWebView的同步本机通信,swift,ios8,wkwebview,Swift,Ios8,Wkwebview,JavaScript和Swift/Obj-C本机代码之间是否可以使用WKWebView进行同步通信 这些都是我尝试过但失败的方法 方法1:使用脚本处理程序 WKWebView接收JS消息的新方法是使用委托方法userContentController:didReceiveScriptMessage:,该方法由window.webkit.messageHandlers.myMsgHandler.postMessage('生命的意义是什么,本机代码?')从JS调用。 这种方法的问题是,在执行本机委托

JavaScript和Swift/Obj-C本机代码之间是否可以使用WKWebView进行同步通信

这些都是我尝试过但失败的方法

方法1:使用脚本处理程序

WKWebView
接收JS消息的新方法是使用委托方法
userContentController:didReceiveScriptMessage:
,该方法由
window.webkit.messageHandlers.myMsgHandler.postMessage('生命的意义是什么,本机代码?')从JS调用。
这种方法的问题是,在执行本机委托方法的过程中,JS执行没有被阻止,因此我们无法通过立即调用
webView.evaluateJavaScript(“something=42”,completionHandler:nil)
返回值

示例(JavaScript)

示例(Swift)

方法2:使用自定义URL方案

在JS中,使用
window.location=“JS://webView?hello=world”
重定向会调用本机
WKNavigationDelegate
方法,可以在其中提取URL查询参数。但是,与UIWebView不同,委托方法不会阻止JS执行,因此立即调用
evaluateJavaScript
将值传递回JS在这里也不起作用

示例(JavaScript)

示例(Swift)

方法3:使用自定义URL方案和IFRAME

这种方法的不同之处在于
window.location
的分配方式。使用空的
iframe
src
属性,而不是直接赋值

示例(JavaScript)

尽管如此,这也不是一个解决方案,它调用与方法2相同的本机方法,这是不同步的

附录:如何使用旧的UIWebView实现这一点

示例(JavaScript)

示例(Swift)


不,我认为这是不可能的,因为WKWebView的多进程架构。WKWebView与应用程序在同一进程中运行,但它与在其自己进程中运行的WebKit进行通信()。JavaScript代码将在WebKit进程中运行。因此,本质上,您要求在两个不同的进程之间进行同步通信,这与它们的设计背道而驰。

我也调查了这个问题,但您失败了。要解决此问题,必须将JavaScript函数作为回调传递。本机函数需要对回调函数求值才能返回结果。实际上,这是JavaScript的方式,因为JavaScript从不等待。阻塞JavaScript线程可能会导致ANR,这非常糟糕


我创建了一个名为的项目,它可以在本机和JavaScript之间建立桥梁。它提供了绑定样式的API,用于从JavaScript调用本机,反之亦然。有一个应用程序。

我发现了一个进行同步通信的黑客,但还没有尝试:


编辑:基本上,您可以使用JS提示符()将负载从JS端传送到本机端。在本机中,WKWebView必须截获提示调用,并确定它是正常调用还是jsbridge调用。然后,您可以将结果作为对提示调用的回调返回。因为提示调用是以等待用户输入的方式实现的,所以javascript本机通信将是同步的。缺点是您只能通过字符串进行通信。

我遇到了一个类似的问题,我通过存储promise回调解决了这个问题

通过WKUserContentController::addUserScript在web视图中加载的js

var webClient = {
    id: 1,
    handlers: {},
};

webClient.onMessageReceive = (handle, error, data) => {
    if (error && webClient.handlers[handle].reject) {
        webClient.handlers[handle].reject(data);
    } else if (webClient.handlers[handle].resolve){
        webClient.handlers[handle].resolve(data);
    }

    delete webClient.handlers[handle];
};

webClient.sendMessage = (data) => {
    return new Promise((resolve, reject) => {
       const handle = 'm' + webClient.id++;
       webClient.handlers[handle] = { resolve, reject };
       window.webkit.messageHandlers.<message_handler_name>.postMessage({data: data, id: handle});
   });
}
var-webClient={
id:1,
处理程序:{},
};
webClient.onMessageReceive=(句柄、错误、数据)=>{
if(错误&&webClient.handlers[handle].reject){
webClient.handlers[handle].reject(数据);
}else if(webClient.handlers[handle].resolve){
webClient.handlers[handle].resolve(数据);
}
删除webClient.handlers[handle];
};
webClient.sendMessage=(数据)=>{
返回新承诺((解决、拒绝)=>{
常量句柄='m'+webClient.id++;
webClient.handlers[handle]={resolve,reject};
window.webkit.messageHandlers..postMessage({data:data,id:handle});
});
}
执行Js请求,就像

webClient.sendMessage(<request_data>).then((response) => {
...
}).catch((reason) => {
...
});
webClient.sendMessage()。然后((响应)=>{
...
}).catch((原因)=>{
...
});
在userContentController中接收请求:didReceiveScriptMessage


使用webClient.onMessageReceive(句柄、错误、响应数据)调用evaluateJavaScript。

WKWebView
这样闪亮的新API缺少Android多年来的功能,这真是太可惜了。这毫无意义。为什么postMessage方法不能返回承诺?更好的方法(在用户体验方面)不是利用异步特性吗?例如,在您向webkit发布的消息中,提供有关从webkit发布操作“结果”的位置的信息。例如,WebKit可以使用stringByEvaluatingJavaScriptFromString调用一个gotSomething()javascript方法?假设您想询问本机代码是否安装了应用程序。如果您使用的是同步调用,那么您可以做一些简单的事情,比如
var isTwitterInstalled=isInstalled('twitter')。如果只有异步调用,则必须将其拆分为两个函数;一个是从JavaScript调用的,另一个是从本机代码调用的,并传递结果。没错,但优点是,如果在iOS世界中执行您试图运行的代码需要任何时间,您的JavaScript根本不会等待它,这使得UI更具响应性。我在这里遇到的问题是,似乎不可能调用JavaScript并使用调用的结果*来响应UI/framework请求。例如,打印或复制。我尝试通过完成处理程序进行同步时,结果都是
var something;
function getSomething() {
    window.location = "js://webView?question=meaning" // Execution NOT blocking here either :(
    return something;
}
getSomething(); // Returns undefined
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler decisionHandler: (WKNavigationActionPolicy) -> Void) {
    webView.evaluateJavaScript("something = 42", completionHandler: nil)
    decisionHandler(WKNavigationActionPolicy.Allow)
}
var something;
function getSomething() {
    var iframe = document.createElement("IFRAME");
    iframe.setAttribute("src", "js://webView?hello=world");
    document.documentElement.appendChild(iframe);  // Execution NOT blocking here either :(
    iframe.parentNode.removeChild(iframe);
    iframe = null;
    return something;
}
getSomething();
var something;
function getSomething() {
    // window.location = "js://webView?question=meaning" // Execution is NOT blocking if you use this.

    // Execution IS BLOCKING if you use this.
    var iframe = document.createElement("IFRAME");
    iframe.setAttribute("src", "js://webView?question=meaning");
    document.documentElement.appendChild(iframe);
    iframe.parentNode.removeChild(iframe);
    iframe = null;

    return something;
}
getSomething();   // Returns 42
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    webView.stringByEvaluatingJavaScriptFromString("something = 42")    
}
var webClient = {
    id: 1,
    handlers: {},
};

webClient.onMessageReceive = (handle, error, data) => {
    if (error && webClient.handlers[handle].reject) {
        webClient.handlers[handle].reject(data);
    } else if (webClient.handlers[handle].resolve){
        webClient.handlers[handle].resolve(data);
    }

    delete webClient.handlers[handle];
};

webClient.sendMessage = (data) => {
    return new Promise((resolve, reject) => {
       const handle = 'm' + webClient.id++;
       webClient.handlers[handle] = { resolve, reject };
       window.webkit.messageHandlers.<message_handler_name>.postMessage({data: data, id: handle});
   });
}
webClient.sendMessage(<request_data>).then((response) => {
...
}).catch((reason) => {
...
});