Even if you are not developing cross-platform applications, JavaScript might come in handy for implementing some calculations that need to work the same way on all platforms, or you might want to reuse an existing JavaScript implementation from your web app. In my case this was code generating graphical output based on plain data.
There are a lot of tutorials available on the web on how to invoke JavaScript from your iOS code with Apple’s JavaScriptCore framework. A good overview of the basics can be found in this article.
In JavaScriptCore a JSContext
object is your execution environment for the JavaScript code. It corresponds to a single global object and is similar to the window object of a web browser. It’s a sandbox and the space where all global variables and functions that you add become accessible by any other object in the same context.
When you start running JavaScript code in a JSContext
you will most likely face a problem soon: Anything that is not part of the pure JavaScript language won’t be available to you. This includes the common functions setInterval
and setTimeout
. Most environments like browsers provide these methods but since JavaScriptCore is a barebones engine, none of these are defined.
In this blog post I will show how these functions can be implemented in Swift on the native side and how to make them available in the JSContext
object.
The first step is to provide native functionality for intervals and timeouts. We create a new Swift class JSPolyfill
for this and define functions to allow creation and removal of repeating and non-repeating timers. The callback value is of type JSValue
and references the JavaScript function which is given as parameter to setInterval
and setTimeout
in the JavaScript code.
Next we need to implement the actual bridge between JavaScript and Swift. Calling setObject(_:forKeyedSubscript:)
of JSContext
lets us load code blocks as polyfills into the context. However this only works with Objective-C blocks, not with Swift closures. In order to export a closure we annotate the closure with the @convention(block)
attribute to bridge it to an Objective-C block.
Add the following method to our JSPolyfill
class which creates the four missing functions for supporting timeouts and intervals.
Now all that’s left to do is to call the registerInContext
method after we instantiate JSContext
.
When running our JavaScript code in this context the native methods will be called every time there is a call to one of the functions. You can check this by setting breakpoints, for example.
Feel free to provide feedback and ask questions.