Search code examples
javascriptioshybrid-mobile-appjavascriptcore

JavascriptCore: pass javascript function as parameter in JSExport


JavascriptCore is a new framework supported in iOS7. We can use the JSExport protocol to expose parts of objc class to JavaScript.

In javascript, I tried to pass function as parameter. Just like this:

function getJsonCallback(json) {
        movie = JSON.parse(json)
        renderTemplate()
}
viewController.getJsonWithURLCallback("", getJsonCallback)

In my objc viewController, I defined my protocol:

@protocol FetchJsonForJS <JSExport>
 - (void)getJsonWithURL:(NSString *)URL
               callback:(void (^)(NSString *json))callback;
 - (void)getJsonWithURL:(NSString *)URL
         callbackScript:(NSString *)script;
@end

In javascript, viewController.getJsonWithURLCallbackScript works, however, viewController.getJsonWithURLCallback not work.

Is there any mistake that I used block in JSExport? Thx.


Solution

  • The problem is you have defined the callback as an Objective-C block taking a NSString arg but javascript doesn't know what to do with this and produces an exception when you try to evaluate viewController.getJsonWithURLCallback("", getJsonCallback) - it thinks the type of the second parameter is 'undefined'

    Instead you need to define the callback as a javascript function.

    You can do this in Objective-C simply using JSValue.

    For other readers out there, here's a complete working example (with exception handling):

    TestHarnessViewController.h:

    #import <UIKit/UIKit.h>
    #import <JavaScriptCore/JavaScriptCore.h>
    
    @protocol TestHarnessViewControllerExports <JSExport>
    - (void)getJsonWithURL:(NSString *)URL callback:(JSValue *)callback;
    @end
    
    @interface TestHarnessViewController : UIViewController <TestHarnessViewControllerExports>
    @end
    

    TestHarnessViewController.m: (if using copy/paste, remove the newlines inside the evaluateScript - these were added for clarity):

    #import "TestHarnessViewController.h"
    
    @implementation TestHarnessViewController {
        JSContext *javascriptContext;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        javascriptContext  = [[JSContext alloc] init];
        javascriptContext[@"consoleLog"] = ^(NSString *message) {
            NSLog(@"Javascript log: %@",message);
        };
        javascriptContext[@"viewController"] = self;
    
        javascriptContext.exception = nil;
        [javascriptContext evaluateScript:@"
            function getJsonCallback(json) {
                consoleLog(\"getJsonCallback(\"+json+\") invoked.\");
                /* 
                movie = JSON.parse(json); 
                renderTemplate(); 
                */
            } 
    
            viewController.getJsonWithURLCallback(\"\", getJsonCallback);
        "];
        JSValue *e = javascriptContext.exception;
        if (e != nil && ![e isNull])
            NSLog(@"Javascript exception occurred %@", [e toString]);
    }
    
    - (void)getJsonWithURL:(NSString *)URL callback:(JSValue *)callback {
        NSString *json = @""; // Put JSON extract from URL here
        [callback callWithArguments:@[json]];
    }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end