Hello, friends in front of the screen, I’m Lao Chen.

Today, this article is the first part of the native and front-end dance module. It will talk to you about JavaScriptCore, the bridge between the front-end and native, and see what it is and what it can do.

To sum up, JavaScriptCore provides dynamic ability to call JavaScript programs for native programming languages Objective-C and Swift, and also provides native ability for JavaScript to make up for the lack of front-end capabilities.

Because of the bridge function of JavaScriptCore, there are many frameworks for App development using JavaScriptCore, such as React Native, Weex, applets, WebView Hybird and so on.

What is the origin of JavaScriptCore? Why do frameworks use the JavaScriptCore engine as a bridge between the front-end and native?

To answer this question, you also need to know the background of JavaScriptCore.

JavaScriptCore, originally the core engine used in WebKit to interpret executing JavaScript code. Engines that interpret executing JavaScript code have been around since JavaScript was first created, and have evolved over time. Today apple has a JavaScript core engine, Google has a V8 engine, Mozilla has a SpiderMonkey. For iOS developers, all you need is a deep understanding of Apple’s JavaScriptCore framework.

Before iOS7, apple did not open the JavaScriptCore engine. If you want to use JavaScriptCore, you have to manually compile it from WebKit, which is C, which is very unfriendly to iOS developers.

But starting with iOS7, Apple began to introduce the JavaScriptCore framework to iOS and provide it as a system-level framework for developers to use. At this point, the interface is wrapped in Objective-C, which is very experience-friendly for native Objective-C developers.

The name of the JavaScriptCore framework is javascriptCore.framework. Since Apple’s systems already have the JavaScriptCore framework built in and perform as well as other engines like V8 and SpiderMonkey, front-end development App frameworks invariably use the JavaScriptCore framework as a bridge between themselves and the native.

Next, I’ll give you a detailed analysis of how the JavaScriptCore framework works.

JavaScriptCore framework

Apple’s official description of the JavaScriptCore framework is available here. Structurally, JavaScriptCore framework is mainly composed of JSVirtualMachine, JSContext, and JSValue classes.

The purpose of JSVirturalMachine is to provide a virtual machine environment for JavaScript code to run. JSVirtualMachine can execute only one thread at a time. If you want multiple threads to perform tasks, you can create multiple JSVirtualMachines. Each JSVirtualMachine has its own Garbage Collector (GC) for memory management, so objects cannot be passed between multiple JSVirtualmachines.

JSContext is the context of the JavaScript runtime environment and is responsible for both native and JavaScript data passing.

JSValue is a JavaScript value object that records JavaScript raw values and provides interface methods for converting native value objects.

The relationship between JSVirtualMachine, JSContext, and JSValue is shown as follows:

As can be seen, JSVirtualMachine contains multiple JSContext, and the same JSContext can have multiple JsValues.

JSVirtualMachine, JSContext, and JSValue classes provide interfaces that enable native applications to execute JavaScript code, access JavaScript variables, and access and execute JavaScript functions. You can also have JavaScript execute native code and use native output classes.

So, how does JavaScriptCore, which executes JavaScript code, interact with native applications?

To understand this, take a look at this graph:

JSVirtualMachine in each JavaScriptCore corresponds to a native thread. The same JSVirtualMachine can use JSValue to communicate with the native thread, following the JSExport protocol: Native threads can provide class methods and properties to JavaScriptCore, and JavaScriptCore can provide JsValues to native threads.

To interact with native applications, JavaScriptCore must first have a JSContext. JSContext is directly initialized with init, and the JSVirtualMachine created by the system is used by default. If the JSContext itself wants to specify which JSVirtualMachine to use, it can do so using the initWithVirtualMachine method as follows:

JSVirtualMachine * JSVM = [[JSVirtualMachine alloc] init]; / / use the JSVM JSContext object ct JSContext * ct = [[JSContext alloc] initWithVirtualMachine: JSVM];Copy the code

Initialize a JSVirtualMachine object, JSVM, and then a JSContext object, ct, using JSVM, as shown in the code above. Here’s another example of calling a JavaScript variable in native code using JavaScriptCore. Here’s a piece of JavaScript code where I define a JavaScript variable I, and then let’s see how to call variable I from a native JavaScriptCore. The code is as follows:

JSContext *context = [[JSContext alloc] init]; [context evaluateScript:@ "var I = 4 + 8"] ; // Convert the I variable to a native object NSNumber *number = [context[@ "i&"] toNumber]; NSLog(@" var i is %@, number is %@" ,context[@"i"] , number);Copy the code

In the above code, JSContext calls the evaluateScript method, which returns the JSValue object.

The JSValue class provides a set of interfaces for converting JavaScript object value types to native types. You can check out the official documentation for a detailed description of the JSValue interface.

There are three commonly used conversion interfaces, which I will expand with you a bit:

  • In this example, we use the toNumber method to convert JavaScript values into NSNumber objects.
  • If the variable in your JavaScript code is an array object, you can use the toArray method to convert it into an NSArray object.
  • If the variable is of type Object, you can use the toDictionary method to convert it to NSDictionary.

If you want to use JavaScript function objects in native code, you can pass in the callWithArguments method and call it. The following is an example:

[Context evaluateScript:@ "Function addition(x, y) {return x + y}"] ; // Obtain the addition function JSValue *addition = context[@" addition&";] ; JSValue *resultValue = [addition callWithArguments:@[@(4), @(8)]]; // Convert the result of the addition function execution into a native NSNumber to use. NSLog(@" function is %@; reslutValue is %@" ,addition, [resultValue toNumber]);Copy the code

First, JSContext gets the addition function in the JavaScript code through the evaluateScript method and saves it as a JSValue object. Then, through the callWithArguments method of JSValue, you pass in the x and y arguments required by the addition function to execute the function.

And if you want to invoke JavaScript functions in native code, you need to use JSValue invokeMethod: withArguments method. For example, the Weex framework uses this method to get JavaScript functions.

Related code path is the incubator – weex/ios/SDK/WeexSDK/Sources/Bridge/WXJSCoreBridge. Mm, the core code is as follows:

- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args { WXLogDebug(@" Calling JS... method:%@, args:%@" , method, args); return [[_jsContext globalObject] invokeMethod:method withArguments:args]; }Copy the code

As you can see, JSContext has a globalObject property. GlobalObject is of type JSValue, which records the globalObject of the JSContext. JavaScript functions executed using globalObject can use the global JavaScript object. Therefore, by globalObject perform invokeMethod: withArguments method will be able to use global JavaScript object. From the above analysis, we can see that JavaScript scripts can be executed in native code using JavaScript value objects and function objects through the evaluateScript method. So how does JavaScript call native code? I’ll give you a code example so you can think about how it works:

// add a subtraction function to JSContext using a native Block [@" subtraction& "].  = ^(int x, int y) { return x - y; }; JSValue *subValue = [context evaluateScript:@ "// call the native subValue function JSValue with JavaScript code in the same JSContext. Subtraction (4, 8); & quot;] ; NSLog(@" Substraction (4, 8) is % @ & quot; ,[subValue toNumber]);Copy the code

First, add a subtraction function using the native Block in JSContext. ● Then call the native subtraction function with JavaScript code in the same JSContext. In addition to blocks, we can also use the JSExport protocol to call native code in JavaScript, so native code makes classes that follow the JSExport protocol available to JavaScript. The Weex framework has a WXPolyfillSet class that follows the JSExport protocol, allowing JavaScript to use the NSMutableSet type in native code. WXPolyfillSet header code path is the incubator – weex/ios/SDK/WeexSDK/Sources/Bridge/WXPolyfillSet. H, content is as follows:

@protocol WXPolyfillSetJSExports < JSExport> // JavaScript can use the method + (instanceType)create; - (BOOL)has:(id)value; - (NSUInteger)size; - (void)add:(id)value; - (BOOL)delete:(id)value; - (void)clear; @end // WXPolyfillSet follows the JSExport protocol @interface WXPolyfillSet: NSObject < WXPolyfillSetJSExports> @endCopy the code

As you can see, WXPolyfillSet provides a set of methods for JavaScript to use through the JSExport protocol.

Now that we understand how native and JavaScript interact, we know that their interaction depends on the Virtual machine environment JSVirtualMachine. Next, we need a deeper understanding of the JavaScriptCore engine to use the framework well. For example, how JavaScriptCore improves performance by directly using cached JIT-compiled machine code, and how some functions are optimized for test compilation.

JSVirtualMachine is an abstract JavaScript virtual machine for developers to develop, and its core JavaScriptCore engine is a real virtual machine, including the virtual machine interpreter and runtime part. The interpreter is mainly used to compile high-level scripting languages into bytecode, and the runtime is mainly used to manage the memory space of the runtime. When you need to debug memory problems, you can use the Web Inspector in JavaScriptCore or manually trigger the Full GC to check for memory problems.

Next, LET me tell you about the internal components of the JavaScriptCore engine.

Components of the JavaScriptCore engine

JavaScriptCore consists of Parser, Interpreter, Compiler, GC, etc. Compiler translates bytecode into machine code and optimizes it. You can check out WebKit’s official introduction to the JavaScriptCore engine by clicking this link.

JavaScriptCore explains the process of executing JavaScript code in two steps.

The first step is lexical analysis and syntax analysis by Parser to generate bytecode.

LLInt (Low Level Interpreter) first executes the bytecode generated by the Parser. JavaScriptCore optimizes the frequently run functions or loops. Optimizers include Baseline JIT, DFG JIT, and FTL JIT. JavaScriptCore uses OSR (On Stack Replacement) to manage multiple optimization level switches.

summary

In this article, I’m going to share with you some of the things that JavaScriptCore can do in iOS.

To sum up, JavaScriptCore provides interfaces for the front-end and native to call each other. On the interface layer, JSContext and JSValue classes are mainly used. The evaluateScript method of JSValue, Block assignment context, and JSExport protocol export are used to achieve interoperability.

The advantage of the front end is the ability to write UI quickly, and the advantage of the native is the natural support for platform features. Now that we have a weapon that can break through the front end and the native, we can take advantage of the advantages of both and complement each other to do more and more interesting things. And you, too, can use your imagination to create more interesting apps.

Green hills never change, green waters always flow. Thank you