Подтвердить что ты не робот

Синхронная внутренняя связь JavaScript с WKWebView

Возможно ли использование синхронной связи между JavaScript и Swift/Obj-C с помощью WKWebView?

Это те подходы, которые я пробовал и потерпел неудачу.

Подход 1: Использование обработчиков script

WKWebView Новый способ получения сообщений JS заключается в использовании метода делегата userContentController:didReceiveScriptMessage:, который вызывается из JS на window.webkit.messageHandlers.myMsgHandler.postMessage('What the meaning of life, native code?') Проблема с этим подходом заключается в том, что во время выполнения собственного метода делегирования выполнение JS не блокируется, поэтому мы не можем вернуть значение, немедленно вызывая webView.evaluateJavaScript("something = 42", completionHandler: nil).

Пример (JavaScript)

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

Пример (Swift)

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

Подход 2: Использование специальной схемы URL

В JS перенаправление с помощью window.location = "js://webView?hello=world" вызывает собственные методы WKNavigationDelegate, где параметры запроса URL могут быть извлечены. Однако, в отличие от 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

Пример (Swift)

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

Пример (Swift)

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    webView.stringByEvaluatingJavaScriptFromString("something = 42")    
}
4b9b3361

Ответ 1

Нет. Я не считаю, что это возможно благодаря многопроцессорной архитектуре WKWebView. WKWebView работает в том же процессе, что и ваше приложение, но он взаимодействует с WebKit, который работает в своем собственном процессе (Знакомство с Modern WebKit API). Код JavaScript будет запущен в процессе WebKit. Таким образом, вы, по сути, просите иметь синхронную связь между двумя разными процессами, которые противоречат их дизайну.

Ответ 2

Я также исследовал эту проблему и потерпел неудачу, как вы. Чтобы обходной путь, вы должны передать функцию JavaScript в качестве обратного вызова. Родной функции необходимо оценить функцию обратного вызова для возврата результата. На самом деле, это JavaScript, потому что JavaScript никогда не ждет. Блокировка потока JavaScript может вызвать ANR, это очень плохо.

Я создал проект под названием XWebView, который может установить мост между родным и JavaScript. Он предлагает интерфейс API с обязательным интерфейсом для вызова native из JavaScript и наоборот. Существует sample приложение.