Amruta Amruta - 3 months ago 26
Swift Question

Call to swift method from JavaScript hangs xcode and application

I am writing an iOS App (using xcode 7.3 and swift 2.2) using JavascriptCode framework. Calling javascript methods from swift works perfect, but when I call the swift method from javascript, xcode simply shows a "loading" type of symbol and nothing happens. I need to "force quit" xcode to get out of this state.
I have followed https://www.raywenderlich.com/124075/javascriptcore-tutorial and http://nshipster.com/javascriptcore/ and I am trying pretty simple calls.

Has anyone faced this kind of issue?

My swift code is as follows:

@objc protocol WindowJSExports : JSExport {
var name: String { get set }
func getName() -> String
static func createWindowWithName(name: String) -> WindowJS
}

@objc class WindowJS : NSObject, WindowJSExports {
dynamic var name: String
init(name: String) {
self.name = name
}
class func createWindowWithName(name: String) -> WindowJS {
return WindowJS(name: name)
}
func getName() -> String {
NSLog("getName called from JS context")
return "\(name)"
}
}


I am initializing the context as follows:

runContext = JSContext()
runContext.name = "test_Context"

windowToJs = WindowJS(name: "test")
runContext.setObject(windowToJs.self, forKeyedSubscript: "WindowJS")


If I replace the last two lines in above code with below code without instantiating it, the code simply fails to load.

runContext.setObject(WindowJS.self, forKeyedSubscript: "WindowJS")


And the javascript code is as simple as

function check() {
return WindowJS.getName()
}


I do see the breakpoint being hit in the JS function check and when the WindowJS.getName gets called, xcode simply becomes unresponsive.

Answer

You're creating a deadlock since you are calling from Swift to JavaScript back to Swift. I'm not sure exactly why it is a deadlock but I had a similar issue with WKWebView on Mac recently.

You need to decouple this and make the communication asynchronous. This obviously means you cannot simply return a value from your JS function in this case.

To decouple, you can break the deadlock by deferring the work the JavaScript function needs to do out of the current runloop iteration using setTimeout:

function myFunction() {
  setTimeout(function() {
    // The actual work is done here.
    // Call the Swift part here.
  }, 0);
}

The whole native ↔︎ JavaScript communication is very, very tricky. Avoid it if you can. There's a project called XWebView that may be able to help you as it tries to ease bridging between the two worlds.

Comments