Calling Objective-C Methods From JavaScript

The web scripting capabilities of WebKit permit you to access Objective-C properties and call Objective-C methods from the JavaScript scripting environment.

An important but not necessarily obvious fact about this bridge is that it does not allow any JavaScript script to access Objective-C. You cannot access Objective-C properties and methods from a web browser unless a custom plug-in has been installed. The bridge is intended for people using custom plug-ins and JavaScript environments enclosed within WebKit objects (for example, a WebView).

How to Use Objective-C in JavaScript

The WebScripting informal protocol, defined in WebScriptObject.h, defines methods that you can implement in your Objective-C classes to expose their interfaces to a scripting environment such as JavaScript. Methods and properties can both be exposed. To make a method valid for export, you must assure that its return type and all its arguments are Objective-C objects or basic data types like int and float. Structures and non object pointers will not be passed to JavaScript.

Method argument and return types are converted to appropriate types for the scripting environment. For example:

Instances of all other classes are wrapped before being passed to the script, and unwrapped as they return to Objective-C.

As an exception, JavaScript arrays cannot be cleanly mapped to NSArray objects because they are a hybrid between a numerically-indexed array and an associative array. To avoid loss of data during the mapping, you must instead use the webScriptValueAtIndex: and setWebScriptValueAtIndex:value: methods.

A Sample Objective-C Class

Let’s look at a sample class. In this case, we will create an Objective-C address book class and expose it to JavaScript. Let’s start with the class definition:

@interface BasicAddressBook: NSObject {
}
+ (BasicAddressBook *)addressBook;
- (NSString *)nameAtIndex:(int)index;
@end

Now we’ll write the code to publish a BasicAddressBook instance to JavaScript:

BasicAddressBook *littleBlackBook = [BasicAddressBook addressBook];
 
id win = [webView windowScriptObject];
[win setValue:littleBlackBook forKey:@"AddressBook"];

Once you expose these methods to JavaScript (described at the end of this section), you should be able to access your basic address book from the JavaScript environment and perform actions on it using standard JavaScript functions.

Now, let’s make an example showing how you can use the BasicAddressBook class instance in JavaScript. In this case, we’ll print the name of a person at a certain index in our address book:

function printNameAtIndex(index) {
    var myaddressbook = window.AddressBook;
    var name = myaddressbook.nameAtIndex_(index);
    document.write(name);
}

You may have noticed one oddity in the previous code example. There is an underscore after the JavaScript call to the Objective-C nameAtIndex method. In JavaScript, it is called nameAtIndex_. This is an example of the default method renaming scheme in action.

Unless you implement webScriptNameForSelector to return a custom name, the default construction scheme is used. It is your responsibility to ensure that the returned name is unique to the script invoking this method. If your implementation of webScriptNameForSelector returns nil or you do not implement it, the default name for the selector will be constructed as follows:

The following table shows example results of the default method name constructor:

Objective-C selector

Default script name for selector

setFlag:

setFlag_

setFlag:forKey:withAttributes:

setFlag_forKey_withAttributes_

propertiesForExample_Object:

propertiesForExample$_Object_

set_$:forKey:withDictionary:

set$_$$_forKey_withDictionary_

Since the default construction for a method name can be confusing depending on its Objective-C name, you would benefit yourself and the users of your class if you implement webScriptNameForSelector and return more human-readable names for your methods.

Getting back to the BasicAddressBook, now we’ll implement webScriptNameForSelector and isSelectorExcludedFromWebScript for our nameAtIndex method. In our BasicAddressBook class implementation, we’ll add this:

+ (NSString *) webScriptNameForSelector:(SEL)sel
{
    ...
 
    if (sel == @selector(nameAtIndex:))
            name = @"nameAtIndex";
 
    return name;
}
 
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
{
    if (sel == @selector(nameAtIndex:)) return NO;
    return YES;
}

Now we can change our JavaScript code to reflect our more logical method name:

function printNameAtIndex(index) {
    var myaddressbook = window.AddressBook;
    var name = myaddressbook.nameAtIndex(index);
    document.write(name);
}

For More Information

For more information about using Objective-C from JavaScript and vice versa, see the following documents: