JavaScriptCore Tutorial for iOS: Getting Started
In this JavaScriptCore tutorial you’ll learn how to build an iOS companion app for a web app, reusing parts of its existing JavaScript via JavaScriptCore. By József Vesza.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
JavaScriptCore Tutorial for iOS: Getting Started
20 mins
Note: Updated for Xcode 8, iOS 10, and Swift 3 on 24-09-2016
Since the introduction of Swift in 2014, its popularity has skyrocketed: according to the TIOBE index from February 2016 it’s already listed in 16th place. But only a few spaces ahead at number 9, you’ll find a language that seems quite the opposite of Swift: JavaScript. Swift puts a lot of effort on compile-time safety, while JavaScript is weakly typed and dynamic.
Swift and JavaScript may look different, but there is one important thing that binds them together: you can use them together to make a slick iOS app!
In this JavaScriptCore tutorial you’ll build an iOS companion app for a web page, reusing parts of its existing JavaScript code. In particular, you’ll learn about:
- The components of the JavaScriptCore framework.
- How to invoke JavaScript methods from your iOS code.
- How to access your native code from JavaScript.
Note: You don’t need to be experienced in JavaScript to follow along. If this JavaScriptCore tutorial has piqued your interest in learning the language, Mozilla Developer Network is an excellent resource for beginners — or you can also choose to skip straight to the good parts. :]
Note: You don’t need to be experienced in JavaScript to follow along. If this JavaScriptCore tutorial has piqued your interest in learning the language, Mozilla Developer Network is an excellent resource for beginners — or you can also choose to skip straight to the good parts. :]
Getting Started
Download the starter project for this tutorial and unzip it. You’ll be greeted by the following folder structure:
- Web: Contains the HTML and CSS for the web app that you’ll be converting to iOS.
- Native: The iOS project. This is where you’ll make all the changes in this tutorial.
- js: Contains the JavaScript code used in the project.
The app is named Showtime; you can use it to search for movies on iTunes by price. To see it in action, open Web/index.html in your favorite browser, enter your preferred price, and hit Return:
To test Showtime on iOS, open the Xcode project residing in Native/Showtime. Build and run the app to take a look:
As you can see, the mobile companion isn’t quite feature-ready, but you’ll fix it shortly. The project already contains some code; feel free to browse through it to get a better idea of what’s going on. The app aims to provide a similar experience to the web page: it will display the search results in a collection view.
What is JavaScriptCore?
The JavaScriptCore framework provides access to WebKit’s JavaScript engine. Originally, the framework had a Mac-only, C API, but iOS 7 and OS X 10.9 shipped with a much nicer Objective-C wrapper. The framework enables powerful interoperability between your Swift/Objective-C and JavaScript code.
Note: React Native is an impressive demonstration of the power of JavaScriptCore. If you’re curious about building native apps with JavaScript, make sure you check out our Introducing React Native tutorial on this site.
Note: React Native is an impressive demonstration of the power of JavaScriptCore. If you’re curious about building native apps with JavaScript, make sure you check out our Introducing React Native tutorial on this site.
In this section, you’ll take a closer look at the API. Under the hood, JavaScriptCore consists of a couple of key components: JSVirtualMachine
, JSContext
, and JSValue
. Here’s how they all fit together.
JSVirtualMachine
JavaScript code is executed in a virtual machine represented by the JSVirtualMachine
class. You won’t normally have to interact with this class directly, but there is one main use case for it: supporting concurrent JavaScript execution. Within a single JSVirtualMachine
, it’s not possible to execute multiple threads at the same time. In order to support parallelism, you must use multiple virtual machines.
Each instance of JSVirtualMachine
has its own heap and its own garbage collector, which means that you can’t pass objects between virtual machines. A virtual machine’s garbage collector wouldn’t know how to deal with a value from a different heap.
JSContext
A JSContext
object represents an execution environment for JavaScript code. It corresponds to a single global object; its web development equivalent would be a window
object. Unlike with virtual machines, you are free to pass objects between contexts (given that they reside in the same virtual machine).
JSValue
JSValue
is the primary data type you’ll have to work with: it can represent any possible JavaScript value. An instance of JSValue
is tied to the JSContext
object it lives in. Any value that comes from the context object will be of JSValue
type.
This diagram shows how each piece of the puzzle works together:
Now that you have a better understanding about the possible types in the JavaScriptCore framework, it’s finally time to write some code.
Invoking JavaScript Methods
Back in Xcode, expand the Data group in the project navigator and open MovieService.swift. This class will retrieve and process movie results from iTunes. Right now, it’s mostly empty; it will be your job to provide the implementation for the method stubs.
The general workflow of MovieService
will look like the following:
-
loadMoviesWith(limit:onComplete:)
will fetch the movies. -
parse(response:withLimit:)
will reach out to the shared JavaScript code to process the API response.
The first step is to fetch the list of movies. If you’re familiar with JavaScript development, you’ll know that networking calls typically use XMLHttpRequest
objects. This object isn’t part of the language itself, however, so you can’t use it in the context of an iOS app. Instead, you’ll have to resort to native networking code.
Within the MovieService
class, find the stub for loadMoviesWith(limit:onComplete:)
and modify it to match the code below:
func loadMoviesWith(limit: Double, onComplete complete: @escaping ([Movie]) -> ()) {
guard let url = URL(string: movieUrl) else {
print("Invalid url format: \(movieUrl)")
return
}
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data, let jsonString = String(data: data, encoding: String.Encoding.utf8) else {
print("Error while parsing the response data.")
return
}
let movies = self.parse(response: jsonString, withLimit: limit)
complete(movies)
}.resume()
}
The snippet above uses the default shared URLSession
session to fetch the movies. Before you can pass the response to the JavaScript code, you’ll need to provide an execution context for the response. First, import JavaScriptCore by adding the following line of code to the top of MovieService.swift, below the existing UIKit import:
import JavaScriptCore
Then, define the following property in MovieService
:
lazy var context: JSContext? = {
let context = JSContext()
// 1
guard let
commonJSPath = Bundle.main.path(forResource: "common", ofType: "js") else {
print("Unable to read resource files.")
return nil
}
// 2
do {
let common = try String(contentsOfFile: commonJSPath, encoding: String.Encoding.utf8)
_ = context?.evaluateScript(common)
} catch (let error) {
print("Error while processing script file: \(error)")
}
return context
}()
This defines context
as a lazy JSContext
property:
- First, you load the common.js file from the application bundle, which contains the JavaScript code you want to access.
- After loading the file, the context object will evaluate its contents by calling
context.evaluateScript()
, passing in the file contents for the parameter.
Now it’s time to invoke the JavaScript methods. Still in MovieService.swift, find the method stub for parse(response:withLimit:)
, and add the following code:
func parse(response: String, withLimit limit: Double) -> [Movie] {
// 1
guard let context = context else {
print("JSContext not found.")
return []
}
// 2
let parseFunction = context.objectForKeyedSubscript("parseJson")
guard let parsed = parseFunction?.call(withArguments: [response]).toArray() else {
print("Unable to parse JSON")
return []
}
// 3
let filterFunction = context.objectForKeyedSubscript("filterByLimit")
let filtered = filterFunction?.call(withArguments: [parsed, limit]).toArray()
// 4
return []
}
Taking a look at the process, step by step:
- First, you make sure the context object is properly initialized. If there were any errors during the setup (e.g.: common.js was not in the bundle), there’s no point in resuming.
- You ask the context object to provide the
parseJSON()
method. As mentioned previously, the result of the query will be wrapped in aJSValue
object. Next, you invoke the method usingcall(withArguments:)
, where you specify the arguments in an array format. Finally, you convert the JavaScript value to an array. -
filterByLimit()
returns the list of movies that fit the given price limit. - So you’ve got the list of movies, but there’s still one missing piece:
filtered
holds aJSValue
array, and you need to map them to the nativeMovie
type.
objectForKeyedSubscript()
a little odd here. Unfortunately, Swift only has access to these raw subscripting methods rather than having them translated into a proper subscript
method. Objective-C can use subscripting syntax with square brackets, however.