Amruta Amruta - 4 months ago 57
Swift Question

Async communication of swift and Javascriptcore

I want to use the Async functionality offered by WKWebView outside web view.
The JS Context option does not provide the Async functionality.

In WKWebView, I write my logic as follows.

func swiftFunc1() {
webView.evaluateJavaScript("jsFunc1(), completionHandler: nil)
}


In javascript code I post a message to swift

function jsFunc1() {
window.webkit.messageHandlers.myMsg.postMessage("call swiftFunc2");
}


The swift code can then call appropriate JS callback as a part of handling message.

But this is dependent upon the webview being the foreground view. If I want to use the JS logic independent of the webview, JSContext is the option. I tried following

func swiftFunc1() {
myCtxt = JSContext()
exportedToJS = exportToJS() //confirms to JSExport and uses @objc
myCtxt.setObject(exportedToJS.self, forKeyedSubscript: "swiftIface")
myFunc = myCtxt.objectForKeyedSubscript("jsFunc1")
myFunc.callWithArguments(nil)
}


Now in javascript code I cannot post a message to swift. If I try to call a swift function as follows, code gets stuck forever.

function jsFunc1() {
swiftIface.swiftFunc2() // This creates a deadklock
}


How can I achieve either of the following without "returning" from the called Javascript function jsFunc1()?


  1. Either post a message to swift so that it can take appropriate action

  2. Or call a swift function so that the appropriate action is taken


Answer

Do I understand you right, if you do not want your javscript to terminate after execution?

If I understood you wrong, maybe following helps (I am not at home at Mac to test, but maybe it works if you modify your code as follows).

Option 1: Blocks

The swiftFunc1 could look like this:

func swiftFunc1() {
    myCtxt = JSContext()
    myCtxt.setObject(unsafeBitCast(swiftFunc2, AnyObject.self), forKeyedSubscript: "swiftFunc2")
    exportedToJS = exportToJS() //confirms to JSExport and uses @objc
    myCtxt.evaluateScript("swiftFunc2()")
}

Your swiftFunc2 would look like this:

let swiftFunc2: @convention(block) Void -> Void = { 
  // do something here
}

Your JS code would look like this:

function jsFunc1() {
   swiftFunc2();
 }

Option 2: JSExport Your have an exported class which is accessible for all javascript:

import Foundation
import JavaScriptCore
@objc class JavascriptHandler: NSObject, JavascriptHandlerExport {
  let context: JSContext = JSContext()

  init () {
    context.setObject(self, forKeyedSubscript: "MyJSHandler") // set the object name for self accessible in javascript code
  }
  func swiftFunc1() {
    context.evaluateScript("MyJSHandler.swiftFunc2();")
  }
  func swiftFunc2 () {
    // do something here
  }
}

Your protocol for the exported class.Here you have to declare all properties and methods you want to use with Javascript.

import Foundation
import JavaScriptCore
@objc protocol JavascriptHandlerExport: JSExport {
  func swiftFunc2 ( ) -> Void
}

With this it should be possible for you to call a function from javascript and still let it continue. You can now access the functions of the class JavascriptHandler from Javascript like this in this example:

MyJSHandler.swiftFunc2();

If you want to seperate the class where your WebView is from the one where your JS logic lies that should also not be a problem. You should also be able to combine the block syntax with the JSExport method.

Let me know if it's not working/behaving as wanted.