31

WKWebView を使用して、JavaScript と Swift/Obj-C ネイティブ コード間の同期通信は可能ですか?

これらは私が試して失敗したアプローチです。

アプローチ 1: スクリプト ハンドラーを使用する

WKWebViewの JS メッセージ受信の新しい方法は、によってuserContentController:didReceiveScriptMessage:JS から呼び出されるデリゲート メソッドを使用するwindow.webkit.messageHandlers.myMsgHandler.postMessage('What's the meaning of life, native code?') ことです。このアプローチの問題は、ネイティブのデリゲート メソッドの実行中に、JS の実行がブロックされないため、値を返すことができないことです。すぐに呼び出せwebView.evaluateJavaScript("something = 42", completionHandler: nil)ます。

例 (JavaScript)

var something;
function getSomething() {
    window.webkit.messageHandlers.myMsgHandler.postMessage("What's the meaning of life, native code?"); // Execution NOT blocking here :(
    return something;
}
getSomething();    // Returns undefined

例(スウィフト)

func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
    webView.evaluateJavaScript("something = 42", completionHandler: nil)
}

アプローチ 2: カスタム URL スキームを使用する

JS では、URL クエリ パラメータを抽出できるネイティブ メソッドを使用してリダイレクトするとwindow.location = "js://webView?hello=world"、ネイティブ メソッドが呼び出さ れます。WKNavigationDelegateただし、UIWebView とは異なり、デリゲート メソッドは JS の実行をブロックしないためevaluateJavaScript、値を JS に戻すためにすぐに呼び出しても、ここでは機能しません。

例 (JavaScript)

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)
}

アプローチ 3: カスタム URL スキームと IFRAME を使用する

window.locationこのアプローチは、割り当て方法のみが異なります。直接割り当てる代わりにsrc、空の属性iframeが使用されます。

例 (JavaScript)

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();

それにもかかわらず、これも解決策ではありません。アプローチ 2 と同じネイティブ メソッドを呼び出しますが、これは同期的ではありません。

付録: 古い UIWebView でこれを実現する方法

例 (JavaScript)

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")    
}
4

5 に答える 5

10

いいえ、WKWebView のマルチプロセス アーキテクチャが原因で、それが可能だとは思いません。WKWebView はアプリケーションと同じプロセスで実行されますが、独自のプロセスで実行される WebKit と通信します (最新の WebKit API の紹介)。JavaScript コードは WebKit プロセスで実行されます。したがって、基本的に、設計に反する2つの異なるプロセス間で同期通信を行うように求めています。

于 2014-12-03T12:34:07.713 に答える
1

現在の RunLoop の acceptInput メソッドをポーリングすることにより、evaluateJavaScript の結果を同期的に待機することができます。これにより、JavaScript が終了するのを待っている間、UI スレッドが入力に応答できるようになります。

これをやみくもにコードに貼り付ける前に、警告を読んでください

//
//  WKWebView.swift
//
//  Created by Andrew Rondeau on 7/18/21.
//

import Cocoa
import WebKit

extension WKWebView {
    func evaluateJavaScript(_ javaScriptString: String) throws -> Any? {

        var result: Any? = nil
        var error: Error? = nil
        
        var waiting = true
        
        self.evaluateJavaScript(javaScriptString) { (r, e) in
            result = r
            error = e
            waiting = false
        }
        
        while waiting {
            RunLoop.current.acceptInput(forMode: RunLoop.Mode.default, before: Date.distantFuture)
        }
        
        if let error = error {
            throw error
        }
        
        return result
    }
}

何が起こるかというと、Javascript の実行中にスレッドは、waiting が false になるまで RunLoop.current.acceptInput を呼び出します。これにより、アプリケーションの UI をレスポンシブにすることができます。

いくつかの警告:

  • UI のボタンなどは引き続き応答します。Javascript の実行中に誰かにボタンを押してほしくない場合は、おそらく UI との対話を無効にする必要があります。(これは、Javascript で別のサーバーを呼び出している場合に特に当てはまります。)
  • evaluateJavaScript を呼び出す際のマルチプロセスの性質により、予想よりも遅くなる場合があります。「インスタント」なコードを呼び出している場合でも、タイトなループで Javascript を繰り返し呼び出すと、速度が低下する可能性があります。
  • これはメイン UI スレッドでのみテストしました。このコードがバックグラウンド スレッドでどのように機能するかはわかりません。問題がある場合は、NSCondition を使用して調査します。
  • これはmacOSでのみテストしました。これが iOS で機能するかどうかはわかりません。
于 2021-07-19T00:49:56.447 に答える