Getting Started with the JavaScriptCore Framework
Introduction My recent project involves Hybrid development, requiring interaction between JavaScript and Objective-C code. Since the JS interface was already in use on the Android side and couldn't be easily changed, I explored several approaches and ultimately chose the framework. The framework is essentially just a wrapper around the C/C++ implementation inside . Classes in JavaScriptCore imports 5 files, each defining a class corresponding to its filename: JSContext and JSValue provides the underlying resources for JavaScript execution. provides the execution environment; you can run a JavaScript script via , and any functions, variables, etc. defined in the script will be stored in the context for later use. Every is created based on a — using . If you initialize using , a new object is automatically created internally before calling the former initializer. acts as the bridge for data conversion between JavaScript and Objective-C, providing a variety of methods to conveniently convert JavaScript data types to Objective-C and back. The type mapping is as follows: Basic Type Conversion Let's start with a simple example: Output: You can also store a JavaScript variable in a and retrieve it using subscript notation. For Array or Object types, can also directly get and set values using subscripts. Output: The output clearly shows that the code successfully assigned data from Objective-C to the JavaScript array. follows JavaScript array behavior: no out-of-bounds errors, with the array automatically expanding. You can also access properties of JavaScript objects through — for example, retrieves the JavaScript array's length. When converted to , all information is correctly transferred. Method Conversion Not only can various data types be converted, but Objective-C Blocks can also be passed into a to serve as JavaScript functions. For example, the method commonly used in front-end development — while JavaScriptCore doesn't include it natively (since it's not running in a browser, there's no , , or ) — you can still define a Block to call to simulate it: This is quite handy for debugging. Output: The Block successfully bridged a JavaScript method call back into Objective-C while still following all the characteristics of JavaScript functions, such as a variable number of arguments. That's why provides class methods to get the argument list () and the current calling object (). In the example above, outputs , which is also what the instance method returns. In JavaScript, all global variables and functions are properties of a global object — in a browser that's ; what it is in JavaScriptCore is left as an exercise for the reader. A Block can be passed into a as a method, but has no method to convert a JavaScript function into a Block for use in Objective-C — since a Block's parameter count, types, and return type are fixed. Although you can't extract a function as a Block, provides to call the function by passing arguments to it. Output: also provides so you can directly call a method on an object. If the method is a global function, you should call it on the 's ; if it's a method on a JavaScript object, call it on the corresponding object. Exception Handling Objective-C exceptions are caught by Xcode at runtime, but JavaScript exceptions that occur within a are only captured and stored in the context's property — they are not thrown outward. Constantly checking whether is is impractical. A better approach is to set an on the , which accepts a block of the form . The default implementation simply assigns the incoming to the incoming 's property: You can assign a new block to so that you're immediately notified when a JavaScript exception occurs: Output: Notes on Using Blocks From the examples above, you can see how powerful Blocks are in JavaScriptCore — they build more bridges between JavaScript and Objective-C, making interoperability more convenient. However, note that whether you pass a Block to a to make it a JavaScript function, or assign it to the property, never directly reference an externally defined or object inside the Block. Instead, pass them as parameters into the Block, or use the class method to get the current context. Failing to do so will cause circular references that prevent memory from being properly released. For example, in the custom exception handler above, the Block assigns to the passed-in parameter , not the externally created object — even though they refer to the same object. This is because a Block strongly retains any externally defined objects it captures, and also strongly retains any Block assigned to it, creating a circular reference () that prevents memory from being properly freed. Similarly, you cannot reference a directly inside a Block from the outside, because every holds a reference to its (), and that also retains the Block, again forming a retain cycle. Key-Value Programming — Dictionary cannot directly convert Objective-C and JavaScript objects, since the two object-oriented approaches differ: the former is class-based, the latter prototype-based. However, all objects can be treated as a collection of key-value pairs, so JavaScript objects can be returned to Objective-C and accessed as types. Similarly, and passed into a can be accessed directly as objects: Code: All code from this post can be found on my GitHub . References: Introduction to iOS 7's JavaScriptCore Framework wiki-JavaScriptCore