
’s been added to the JSVirtualMachine using addManagedReference:withOwner method and has not been removed using the removeManagedReference:withOwner method. Otherwise, the JSManagedValue gets set to nil, releasing the JSValue, and is free to be garbage-collected on the JavaScript side.
JSExport
A JSValue can represent and convert all of the JavaScript builtin types to Objective C/Swift and can convert them in the other direction to JavaScript types. However, a JSValue can’t convert Objective C/Swift classes to JavaScript objects without help. The JSExport protocol provides a way to convert Swift/Objective C classes and their underlying instance methods, class functions, and properties into JavaScript objects.
By default when using JSValues, JavascriptCore will convert a Swift/Objective C class into JavaScript object but will not populate instance methods, class functions, or properties. You have to choose which of these you want to expose to JavaScript in your JSExport protocol definition.
JavaScriptCore in Practice
Now that we have described each of the classes and protocols in the JavaScriptCore library, its time to put them to use with some examples.
Setup
Our first step is to create a JSVirtualMachine instance along with a JSContextinstance. If you don’t intend to use concurrent operations via threading, you can skip creating a JSVirtualMachine and just create a JSContext with an empty constructor so a JSVirtualMachine will be created for you. Otherwise, you should create and save a JSVirtualMachine instance and pass that as an argument when creating your JSContext instances.

Calling Functions and Evaluating Scripts
What’s really cool about all of this is that you have the same JavaScript engine that powers WebKit (Safari for iOS and MacOS). Time to ramp this up a bit. I love developing for iOS but I also spend a lot of time developing for Node.js and the web also. I often use JavaScript libraries to cut down on the amount of code I have to write and because there are smarter developers than me out there that have already done some heavy lifting and created some pretty useful stuff. So, our next step is see how we could include and use an existing JavaScript library in our code.
For our example, I am going to use the Moment.js to facilitate a few date time manipulations. By the way, I’m using a Playground for all of my experiments. If you want to follow along, then create a new Playground file with a target of iOS. From there you need the JavaScript code for the Moment.js library, which I installed locally using Bower. Next, I copied the moment dir into my Resources folder for my Playground. If you have trouble with that, you can refer to one of my other posts on using Playgrounds.
What we need to do next is find the path to the moment.js file, copy the contents of the file into a String, and inject it into our JSContext. See below for how you might accomplish that.
The important take-away here is that if you call a JSContext’s evaluateScript()method you are executing code that, in our case, adds the entire moment.jslibrary as a global object for that particular JSContext. Any scripts or JavaScript objects that are added later will be able to use all of moment.js’s functionality.
Well, at least that sounded good. The truth is that it depends on the content of the script you inject into the JSContext. For the Moment.js lib to work, we need to call it’s constructor and format function via another evaluateScriptcall. Once we do that, we can get a reference to the moment.js library by using our JSContext’s objectForKeyedSubscript method, which will give us the initialized moment.js object. Refer to the sample above for some examples of how you can invoke a method or call constructors with arguments, which — behind the scenes — make the right things happen in JavaScript. It’s all pretty powerful.
Extended Example
Moving right along, I want provide a more in-depth example to get you thinking about the possibilities of actually using this in your own code. We are going to create a small app for showing contacts. Only instead of real contacts, we are going to use the JavaScript library Faker to provide us new contact details each time we execute. Our example will allow us to model the contact object in Swift and create functions in Swift that can be executed in JavaScript. We also need to get back data from the JavaScript side that has the “faked” contact details. Last for good measure, we will use a WKWebView instance to display our contact in a view on our Playground.
The first step is to create a contact object and a JSExports protocol that outlines what we want exposed to JavaScript. After that we need to use the setObject method of our JSContext to make our contact object accessible in JavaScript.
Our next step is to create a Swift function that we can execute in JavaScript. We need to use the @convention(block) syntax to convert our Swift closure to a block that will become a JavaScript function with the same parameters and return values. We again call the setObject method of the JSContext to pass our method to JavaScript. However, this time we need to use the unsafeBitCastmethod to convert the Swift closure into the AnyObject type for JavaScript to process correctly.
Our next series of operations is designed to show you how to call and use the contact object and our createContact function from JavaScript. You will create a contact.js script and add it to the Resources folder of the Playground. Our script is going to have one function that creates the fake contact using the Faker.js library. It’s purpose is to call the method to add the new contact to a view in the Playground and return the created contact for further inspection.
The code above creates a JSExports protocol and class for another object we would like to expose to JavaScript. We also add this new class to our JSContext so that it is accessible via JavaScript. In addition, we create the Swift method to add the new contact object to a view we can see in our Playground.
Next we need to add the Faker.js library to our JavaScript environment. We follow the same pattern we used for adding Moment.js. We also add our contact.js file to our environment, which adds the createFakeContact() method to our global scope. We then get a reference to the createFakeContact()method on the native side and execute it. I realize there is a lot of back and forth in terms of what’s happening in JavaScript versus what’s occurring in Swift but that’s frankly the point. I want you to see that you can pretty easily pass objects back and forth and execute methods from either side.
The last few things we need to do is to add the page.html file, which is loaded by the WKWebView in our ContactDrawable class.
To get our contact to display, I created a UIView and assigned it to XCPlaygroundPage.currentPage.liveView. Executing the Playground displays a simple list with randomly created “faked” contact details.
Wrapping Up
The Playground displays a view with contact details that change on each run. With a little imagination, you can adapt the example to make native UI objects that you could create and control from JavaScript. Another possibility would be to extend your app with scripts that you download from a server to instantiate Swift classes to perform native tasks.
On a lighter note, I also wrote an article on why creating native apps is probably the best way to go in most of your mobile development endeavors. Read it and weigh in the discussion!
Comments
Post a Comment