Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WKWebView: trying to query javascript synchronously from the main thread

Is there any way to query the javascript synchronously from the main thread?

Javascript is queried from the native code using an asynchronous function with a callback parameter to handle the response:

func evaluateJavaScript(_ javaScriptString: String, completionHandler completionHandler: ((AnyObject!, NSError!) -> Void)?)

Asynchronous behavior can usually be turned synchronous by pausing the thread & controlling execution with a semaphore:

// Executing in the main thread
let sema = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
// Background thread
  self.evaluateJavaScript("navigator.userAgent", completionHandler: { (value:AnyObject!, error: NSError!) -> Void in
    if let ua = value as? String {
      userAgent = ua
    } else {
      ERROR("ERROR There was an error retrieving the default user agent, using hardcoded value \(error)")
    }
    dispatch_semaphore_signal(sema)
  })
}
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)

...however in this case, because the completionHandler is always called on the main thread, the code deadlocks because the completionHandler block will never execute (main thread was paused by the dispatch_semaphore_wait on the last line)

Any suggestions?

EDIT

I would rather not be blocking the main thread to execute that code. But I can't decouple from the main thread without changing my APIs from synchronous to asynchronous, with a domino effect all the way up the stack (e.g. from let ua = computeUserAgent() to computeUserAgent() {(ua: String)->Void in /*Use ua value here */}). So I need to choose between 2 approaches that both have downsides, and I would rather pick the approach that doesn't mess up my internal APIs, especially for a task as trivial as looking up the user agent.

like image 278
Hugo Avatar asked Feb 07 '15 22:02

Hugo


1 Answers

If you must do this...

As suggested in a comment to this answer you could run a tight loop around your semaphore wait like this.

while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) { 
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode 
                             beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
like image 55
Tristan Burnside Avatar answered Sep 28 '22 20:09

Tristan Burnside