5

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.

2
  • How is the JavaScript call triggered? Is it by chance triggered by calling it (even as a side-effect) from Swift? E.g., is it possible that you end up with a Swift->JS->Swift cycle? Commented Jun 19, 2016 at 14:13
  • @DarkDust Thanks! That is what is happening. How can I achieve an asynchronous effect like that of "postMessage in JavaScript in WKWebView" in a JSContext? Commented Jun 19, 2016 at 16:02

2 Answers 2

4

The setTimeout could be solved by adding following piece of code to my swift function.

let setTimeout: @convention(block) (JSValue, Int) -> () = 
{ callback, timeout in
    let timeVal = Int64(timeout)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeVal), dispatch_get_main_queue(), { callback.callWithArguments(nil)})
}

To expose this native code to the JS context, I also added following.

runContext.setObject(unsafeBitCast(setTimeout, AnyObject.self), forKeyedSubscript: "setTimeout")

Things then worked fine.

Sign up to request clarification or add additional context in comments.

Comments

2

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.

2 Comments

Thanks! I get an error for setTimeout. Do you need to do anything special for setTimeout( /*someCode */, 0) to work? I need that too.
You need to pass it a function. Search the web, there's tons of examples on how to use it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.