Wesley de Groot's Blog
JavaScriptCore

Back

Whether you love it or hate it, JavaScript has become the most important language for developers in the world today. Yet despite any efforts we may take to change or replace it we’d be hard-pressed to deny its usefulness.

What is JavaScriptCore?

JavaScriptCore is a framework that provides a JavaScript engine for macOS and iOS. It allows developers to run JavaScript code within their applications. JavaScriptCore is a part of the WebKit framework and is used by Safari, Mail, and many other applications on macOS and iOS.

JavaScriptCore / Objective-C / Swift Types

JavaScriptCore uses a set of types to represent JavaScript values in Objective-C and Swift. These types are used to interact with JavaScript code and pass values between JavaScript and native code.
Here is a table that shows the mapping between JavaScript types and Objective-C/Swift types:

JavaScript Type JSValue method Objective-C Type Swift Type
string toString NSString String!
boolean toBool BOOL Bool
number toNumber NSNumber NSNumber!
number toDouble double Double
number toInt32 int32_t Int32
number toUInt32 uint32_t UInt32
date toDate NSDate Date?
array toArray NSArray [Any]!
object toDictionary NSDictionary [AnyHashable: Any]!
class toObject custom type custom type

Simple Example

Here’s a simple example that evaluates a JavaScript expression and prints the result:

import JavaScriptCore

let context = JSContext()!
let result = context.evaluateScript("1 + 2 + 3")
result?.toInt32() // 6

Handling Exceptions

If a script throws an exception, JavaScriptCore calls the exception handler you provide. The handler receives the context and the exception object, which you can use to get the exception message.
below is an example how to create an exception handler:

import JavaScriptCore

let context = JSContext()!
context.exceptionHandler = { context, exception in
    print(exception!.toString())
}

context.evaluateScript("**INVALID**")
// Prints "SyntaxError: Unexpected token '**'"

Note: You can’t tell whether a script evaluated successfully based on its return value. For instance, both variable assignment and syntax errors produce undefined return values.

(Hacky) Way to dertimine if a script was evaluated successfully

If we want to determine if a script was evaluated successfully, we can set a variable in the script and then check if that variable exists in the context.
if the variable exists, the script was evaluated successfully.

import JavaScriptCore

let context = JSContext()!
let result = context.evaluateScript("1 + 2 + 3; var didRun = true;")
let didRun = context.objectForKeyedSubscript("didRun")?.toBool()

Virtual Machines and Contexts

JavaScriptCore uses a virtual machine to manage memory and resources. A virtual machine can have multiple contexts, each of which is a separate JavaScript environment. Contexts are isolated from each other, so they can’t share variables or functions.

let queue = DispatchQueue(label: "myVirtualMachineQueue")
let vm = queue.sync { JSVirtualMachine()! }
let context = JSContext(virtualMachine: vm)!

Creating a function in JavaScript

JavaScript evaluation isn’t limited to single statements. When you evaluate code that declares a function or variable, it’s saved into the context’s object space.

context.evaluateScript("""
function twoTimes(number) {
    return number * 2;
}
""")

context.evaluateScript("twoTimes(5)")?
       .toInt32() // 10

Getting JavaScript Context Values from Swift

You can access named values from a JSContext by calling the objectForKeyedSubscript(_:) method. This method returns a JSValue object that represents the value in the context.

context.evaluateScript("var twoTimesFive = twoTimes(5)")

context.objectForKeyedSubscript("twoTimesFive")?
       .toInt32() // 10

Setting Swift Values on a JavaScript Context

You can set a Swift value in a JSContext by calling the setObject(_:forKeyedSubscript:) method. This method creates a new variable in the context with the given name and value.

context.setObject(10, forKeyedSubscript: "swiftValue")

context.evaluateScript("swiftValue")?
       .toInt32() // 10

Passing Functions between Swift and JavaScript

You can pass Swift functions to JavaScript by creating a JSValue object from a block. This object can be called from JavaScript as if it were a regular JavaScript function.

let swiftFunction: @convention(block) (Int) -> Int = { 
    return $0 * 2
}
let jsFunction = JSValue(object: swiftFunction, in: context)
context.setObject(swiftFunction,
                  forKeyedSubscript: "swiftFunction" as NSString)

context.evaluateScript("swiftFunction(5)")?
       .toInt32() // 10

context.objectForKeyedSubscript("swiftFunction")?
       .call(withArguments: [6]) // 12

Passing Swift Objects between Swift and JavaScript

To improve interoperability between language contexts, JavaScriptCore provides the JSExport protocol, which allows native classes to be mapped and initialized directly.

import JavaScriptCore

@objc protocol MyExport: JSExport {
    var value: Int { get set }
    func doubleValue() -> Int
    func doubleTheValue(value: Int) -> Int
}

class MyObject: NSObject, MyExport {
    var value: Int = 0

    func doubleValue() -> Int {
        return value * 2
    }

    func doubleTheValue(value: Int) -> Int {
        return value * 2
    }
}

let context = JSContext()!
context.setObject(
    MyObject.self, 
    forKeyedSubscript: "myObject" as NSString
)

context.evaluateScript("""
var obj = new myObject();
obj.value = 5;
obj.doubleValue(); // 10
""")

context.evaluateScript("""
myObject.doubleTheValue(5) // 10
""")

Conclusion

JavaScriptCore is a powerful framework that allows you to run JavaScript code within your applications.
JavaScriptCore provides a simple API for evaluating JavaScript code, handling exceptions, creating functions, and passing values between Swift and JavaScript. By using JavaScriptCore, you can add dynamic scripting capabilities to your applications and create powerful, interactive user experiences.

Resources:

Read more

Share


Share Mastodon Twitter LinkedIn Facebook
x-twitter mastodon github linkedin discord threads instagram whatsapp bluesky square-rss sitemap