How to create you own native bridge
Chapter 1: Designing an architecture
I’ve been using React Native for a while before I started to dive into the codebase to see how it works. Since then I’ve made quite a few contributions to the project and even managed to become a part of react-native core team.
No doubt, it is pointless to write code without having a clear mental picture of the finished product (or a milestone). Architectural design is an important step of software development. I will try to guide you through the thought process and explain in detail what decisions were made and why.
When you start a new project, it makes sense to determine a list of technologies you’re going to use. In my case they were:
- Objective C to manipulate Cocoa UI
- C++ to work with JSVM
- JSVM (V8 or Chakra)
- React + custom renderer
I assumed my main executable will be written in C++. It will run a JS engine with a patched global context (all major JS engines provides you a possibility to extend their built-in functionality by custom objects/functions). Don’t be afraid of “patched global context”, it means nothing but adding a few new functions written in C++ to your JS environment. Later on, we will talk about it in details.
All these things can be run in the same (main) thread, but it’ll cause certain performance issues. To avoid that, we dedicate a new thread to handle Objective C ↔ UI jobs.
Even though this approach makes sense, it doesn’t work.
The problem is that Apple doesn’t allow you to render your UI in non-main thread. Moreover, if UI can be rendered only in the main thread, it means that Objective C should be run in the same thread as well. Unfortunately, this approach ties your application’s entry point to the platform (or, at least, makes it really complicated to run it on the other platforms). But we have what we have. Let’s change our initial approach to fit this requirement.
In the main thread, Objective C process is given a command and renders an appropriate interface element for it. If there are no errors, Objective C triggers a callback that has been passed in from C++ in order to call a function representing a callback from JS.
Now, let’s try to combine all these together:
- We start a native Cocoa Application because we need to run UI in the main thread.
- Once we receive a command from JS to draw UI, we dispatch it to Objective C. In it’s turn, Objective C parses the command and renders appropriate UI elements.
- After that, Objective C invokes a sequence of callbacks in order to pass a return value to JS.
Building a platform foundation
Time for the hands on experience! First of all, let’s create a blank OS X Cocoa Application:
Once this step is done, you have your foundation. Now, if you open your AppDelegate.m, you will find two application lifetime hooks: applicationDidFinishLaunching and applicationWillTerminate. Our application should spawn a new thread at the moment of creation, so let’s change our applicationDidFinishLaunching to do the trick:
Now I have to answer two questions:
- What is a _jsvmThread variable?
- What does @selector(runJSVMThread) mean?
_jsvmThread is nothing else but an instance variable that stores a reference to our new NSThread. We need this reference in order to execute our C++ callbacks in the proper thread.
Selector, according to Apple’s docs, is the name used to select a method to execute for an object, or the unique identifier that replaces the name when the source code is compiled.
In other words, we initialize a new NSThread and run runJSVMThread method of object self (which points to the current instance of the class) in it.
Here’s the body of runJSVMThread method:
As you can see, I decided to use ChakraCore instead of V8. There are some reasons for that:
- It took me 1 hour to compile V8 and 2 hours to run HelloWorld example
- ChakraCore took me 10 mins to compile and run HelloWorld example
Side note: since we’re building a prototype, I would prefer better developer experience that I have with ChakraCore.
Right after ChakraProxy initialization we have a run loop. We use it here to keep our thread alive (otherwise it’ll die after run function is finished). If you want to know more about run loops, Apple docs is a good place to start.
Now we have an application that starts, spawns a new thread and waits for commands. Next step would be to define a ChakraProxy class.
ChakraProxy is just a container which encapsulates Objective C and C++ code:
Of course, this code doesn’t do anything handy yet, but gives you a room to maneuver. In the next chapter we’ll add some fancy things like ChakraCore, basic bridging model and way, way more.
In the meanwhile, you can play with the code in my github repository.
All these and even more awaits you in the Chapter 2: JSVM and the first adventure.