Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling [JSValue callWithArguments:] locks UI when alert() is called

Using the JSContext from a UIWebView I have created a javascript function that is implemented as an Objective C block:

JSContext *js = ... //get contect from web view
js[@"aFunc"] = ^(JSValue *aString, JSValue *callback) {
    NSString *realString = [aString toString];
    MyOperation *op = [[MyOperation alloc] initWithString:realString andCallback:callback];

    //Do some heavy lifting in background
    [self.myQueue addOperation:op];
}

This function takes a callback as an argument and performs some work in an NSOperationQueue before calling the callback like:

- (void)main {
    JSValue *arg = [self theHeavyWork];
    //Now we have finished the heavy work, switch back to main thread to run callback (if any).
    if ([self.callback isObject] != NO) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.callback callWithArguments:@[arg]];
        });
    }
}

This works fine, unless the callback contains a call to alert():

//This javascript is part of the page in the UIWebView
window.aFunc("important information", function(arg) { alert("Got " + arg); });

In this case the alert shows and the UI becomes completely unresponsive. I am assuming that the event the touch event to close the alert is being blocked by the alert being there.

If I call the callback without the dispatch (in other words on which ever thread the MyOperation is running on) it works just fine, but I was under the impression that any code that could have UI implications (in other words any JS callbacks) should always be run on the main thread. Am I missing something, or do is it really impossible to safely use alert() when using the JavaScriptCore framework?

like image 893
erm410 Avatar asked Apr 05 '14 04:04

erm410


1 Answers

After a couple days looking at stack traces of threads waiting for each other, the solution was so simple I'm not surprised I overlooked it in favor of trying more complicated stuff.

If you want to call back into a UIWebView's javascript asynchronously, use window.setTimeout and let the JSVirtualMachine take care of queuing the callback.

Just replace

dispatch_async(dispatch_get_main_queue(), ^{
    [self.callback callWithArguments:@[arg]];
});

with

dispatch_async(dispatch_get_main_queue(), ^{
    [self.callback.context[@"setTimeout"] callWithArguments:@[self.callback, @0, arg]];
});
like image 101
erm410 Avatar answered Sep 30 '22 03:09

erm410