JavaScriptCore learning: JavaScriptCore
JavaScriptCore framework class
The JavaScriptCore framework actually exposes a very small number of classes. The advantage of this is that the API is very simple. As shown in, there are only five classes: JSContext, JSValue, JSManagedValue, JSVirtualMachine, and JSExport. The core of these classes is JSContext and JSValue. We usually deal with these two classes.
The basic introduction of these classes is as follows:
| JSVirtualMachine |
A JSVirtualMachine instance represents a self-contained environment for JavaScript execution. |
| JSContext |
A JSContext object represents a JavaScript execution environment. |
| JSValue |
A JSValue instance is a reference to a JavaScript value. |
| JSExport |
The JSExport protocol provides a declarative way to exportObjective-C instanceClasses and their instance method, class methods, and properties to JavaScript code. (Note that only OC can be used here) |
| JSManagedValue |
A JSManagedValue object wraps a JSValue object, adding "conditional retain" behavior to provide automatic memory management of values. |
JSVirtualMachine
"A JSVirtualMachine instance represents a self-contained environment for JavaScript execution. ", this section is the definition of JSVirtualMachine in the official document. We use JSVirtualMachine for two main purposes: Support for concurrent JavaScript execution and management for linking JavaScript with OC (Swift) memory of the Code object.
1. Threading and Concurrent JavaScript Execution
Each JavaScript content (a JSContext Object) belongs to one VM (virtual machine), but a virtual machine can contain multiple context. At the same time, for multiple contexts running on the same VM, the VM runs on them to pass values (JSValue Object) to each other ). However, different virtual machines are isolated from each other, that is, you cannot pass the value in one VM to the context of another VM.
JavaScriptCore API is thread-safeFor example, you can create JSValue or execute scripts in any thread. However, other threads trying to use this VM at the same time will be in the waiting state. To execute JS concurrently in multiple threads, create a separate JSVirtual Machine for each thread.
2. Managing Memory for Exported Objects
Note: When we export an OC or Swift object to JS, the corresponding JS values cannot be stored in this object. This behavior will lead to a retain cycle. JSValue objects holds strong references to their corresponding JSContext, and JSContext holds strong references from export to JS native object, that is, native object (OC or Swift object)-> JSValue-> JSContext-> native object.
The solution is to use JSManagedValue to conditionally hold the JavaScript value and report the native ownership chain of the managed value to the VM.
Refer: https://developer.apple.com/library/ios/documentation/JavaScriptCore/Reference/JSVirtualMachine_Ref/index.html
JSContext
"A JSContext object represents a JavaScript execution environment. ", through JavaScript Context, we can execute JavaScript scripts in OC or Swift to access defined or calculated values in JS, and even directly access objects, methods, and functions in native in JS.
| -Init/-initWithVirtualMachine: |
The two initialization methods are different:-init creates a new VM to execute the code, while-initWithVirtualMachine: Allows multiple contexts to share one VM. Refer to the above description about JSVirtualMachine. |
| -EvaluateScript:/-evaluateScript: withSourceURL |
1. evaluating a script runs any top-level code and adds function and object definitions to the context's global object.2. synchronous thread security 3. sourceURL does not affect JavaScript Execution, but it is used to facilitate debugging when exceptions occur. |
| + CurrentContext/+ currentCallee/+ currentThis/+ currentArguments |
The usage of these methods is relatively good, "Call this method within an Objective-C or Swift block or method invoked from within JavaScript ". That is, when we call native block or method in JS, in these native blocks or methods, we can call these methods to obtain execution information about JS. |
| GlobalObject |
"The JavaScript global object associated with the context. (read-only )". If you have studied JS, you will certainly be familiar with the concept of global object. For example, in the browser window, to solve the naming conflict problem that may occur at any time in the window, you also need to execute a specific syntax to execute JS. "Outside of web-browser use, a context's global object serves a similar role, separating the JavaScript namespaces of different contexts. global variables within a script appear as fields or subscripts in the global object. in JavaScriptCore, The global execution environment of JS is the global object, and the global variables and functions defined are created as the attributes and methods of the global object. There is no additional explanation here. For details, refer |
| Exception/exceptionHandler |
An exception occurred when calling JavaScript functions. |
| VirtualMachine |
Refer to the above introduction to VM |
| Name |
Used for remote debugging |
| -ObjectForKeyedSubscript:/-setObject: forKeyedSubscript: |
The OC-based Object subscripting Syntax of JSContext (reference: http://nshipster.com/object-subscripting/) allows us to implement objects and functions interaction between JavaScript and Native in a very simple way. We can use this syntax to operate JSContext or JSContext. golbalObject. |
Refer: https://developer.apple.com/library/ios/documentation/JavaScriptCore/Reference/JSContext_Ref/
JSValue
A JSValue instance is a reference to a JavaScript value.
Every JSValue instance comes from the JSContext that contains this value. This value contains a strong application to the context object. Note that memory leakage may occur if you do not pay attention to it. This means that the context can be released only when all JSValue contained in the context is released. When we call a method through JSValue, the returned JSValue and the previous JSValue belong to the same context. Another point is about VM. The context attribute of JSValue will also be associated with a specific JSVirtualMachine. As mentioned earlier, JSValue can only be transmitted in the same VM, if it is passed to another VM, an OC exception will occur.
The Conversion Relationship Between JavaScript and OC/Swift data types is as follows:
The JSValue variable is a reference to JavaScript values. We can use the JSValue class to convert some basic values (such as numbers and strings) between JavaScript and OC (Swift,
Refer: https://developer.apple.com/library/ios/documentation/JavaScriptCore/Reference/JSValue_Ref/
JSExport
"The JSExport protocol provides a declarative way to export Objective-C instance classes and their instance methods, class methods, and properties to JavaScript code ."
You can use JSExport to Exporting OC objects to JavaScript. When we create a JavaScript Value through an OC instance, and the JSValue class does not specify the Value assignment feature, JavaScriptCore creates a JavaScript Wrapper Object to wrap this OC instance. (For a specific class, JavaScriptCore automatically assigns values to the appropriate JavaScript type. For example, NSString instances is changed to JavaScript strings ).
In JS, inheritance is implemented through the prototype chain. For each of our export OC classes, JavaScriptCore creates a prototype in the corresponding JavaScript context. For NSObject class, the corresponding prototype object is the Object prototype of JavaScript Context. For other OC classes, JavaScriptCore creates a prototype. The internal [Prototype] attribute of this protocol directs to the protocol created by JavaScriptCore for its super class. In this way, the prototype chain of JavaScript wrapper object can reflect the class inheritance relationship of wrapper.
In addition, JavaScriptCore creates a JavaScript constructor for each OC class.
By default, all methods and attributes of OC class are not exposed to JavaScript, but you must select the method and attribute to be exposed. For any protocol implemented by class, if it contains the JSExport protocol, JavaScriptCore considers the list of methods and attributes contained in this protocol to be exposed to JavaScript. At this time, we can call the exported method and attribute of OC class in JavaScript.
In the following section, we need to pay special attention to the custom class that implements the JSExport protocol. JavaScriptCore only creates a JavaScript wrapper object to use the wrap OC class, rather than directly assigning the copy value to the string primitive data type of JavaScript like NSString.
Because the Declaration format of the OC message differs greatly from that of the JavaScript function declaration format, there are also differences in calling methods. There are two basic conversion rules: 1. Remove all colons. 2. uppercase words before the colon. For example: doFoo: withBar, the JavaScript call format is doFooWithBar. Of course, you can also use the JSExportAs macro to optimize the name of the method called in JS. This macro only applies to selector with parameters, for example:
Refer: https://developer.apple.com/library/ios/documentation/JavaScriptCore/Reference/JSExport_Ref/
JSManagedValue
The memory management of OC is a reference technology, and the memory management mechanism of JS is garbage collection. However, in most cases, we do not need to consider the conversion between them. JavaScriptCore has already helped us solve the problem. Note that JSValue holds a strong reference to JSContext. If we want to save JSValue In the OC class, it will easily cause memory leakage. To solve this problem, JavaScriptCore introduces JSMangedValue, which is combined with two methods of JSVirtualMachine (-addManagedReference: withOwner: And-removeManagedReference: withOwner) to solve the problem of circular applications.
A JSManagedValue object wraps a JSValue object, adding "conditional retain" behavior to provide automatic memory management of values. the primary use case for a managed value is to store a JavaScript value in Objective-c or Swift object that is itself exported to JavaScript.
The "conditional retain" feature of managed value ensures that the underlying JavaScript value of managed value will not be released if any of the following conditions is true:
The JavaScript value is reachable through the JavaScript object graph (that is, not subject to JavaScript garbage collection) The JSManagedValue object is reachable through the Objective-C or Swift object graph, as reported to the JavaScriptCore virtual machine using the addManagedReference: withOwner: method that is to say, the JSManagedValue memory is managed by the GC mechanism of JavaScript and the reference technical mechanism of OC/Swift at the same time, JSManagedValue is released only when both are released.
The JSMangedValue class is a weak reference of ARC. If addManagedReference: withOwner: method is not used to add "conditional retain", after the GC of JS destroys underlying JavaScript value, managed value automatically sets the value to nil.
Refer: https://developer.apple.com/library/ios/documentation/JavaScriptCore/Reference/JSManagedValue_Ref/index.html
Debug
Apple provides a very convenient debugging tool for the front-end. This tool also works for JavaScriptCore.
Start the Simulator, and then open safari. In the safari menu, choose Develop> Simulator> JSContext. When we open the tool, we will find a Web Inspector. We can execute some JS directly on the console, and we can find that this debugging tool is very convenient.
If you have some debugging experience on the front-end, you may wonder why Resource and Debugger are empty and have no content. This involves the previous Debug API-evaluateScript: withSourceURL:. The source URL is used here, but note that this API was introduced from 8.0. Let's modify the API call and use the method to execute the JS Code:
Run it again. Open the development tool of safari and you will find that both Resource and Debugger have content. At this time, you can directly debug the JS Code in this tool. The Basic Debugging functions such as interrupt points and single-step execution are available.
In addition to the source URL specified above, the debug APIs mentioned above also have the name of JSContext. What is the role of this API? For example, our App has multiple JSContext. How can we choose to use the Safari develop tool? If we specify a name for JSContext, this problem can be solved. If we set the context name to JSCoreDemo, we will find that the name selected by Develope is also changed to JSCoreDemo.
Pitfall
First, the basic issue of memory loop reference will not be mentioned. What should be forced out is not to capture JSContext in the block, but to use [JSContext currentContext] to obtain the current context, avoid circular applications.
Second, when I was writing the test code, I encountered a very strange problem. If we assign the block or Object (JSExport) directly to the global object attribute of context through the subscripting syntax, then set it to nil again, and the memory will not be released! These OC objects will not be released until JSContext is released.
This has something to do with the GC mechanism of JavaScript. I spoke with the hursing Daniel of UC and tried to use this variable as a variable of globalObject, or use delete to delete the variable (it seems that V8 can call this method to release it immediately ). The final result is that JavaScript cannot be recycled immediately, and the OC objects cannot be released.
The final solution is to call JavaScriptCore's API: JSGarbageCollect to force GC operations. This API problem will cause performance problems. Frequent GC will have a great impact on JS performance. However, if we encapsulate JavaScritCore in a relatively simple way, calling this method should have little impact.
Third, objects without confirms to JSExport can also be passed to the JS execution environment. However, it should be noted that JS processes such objects simply by using wrap and can be passed, however, you cannot call any methods and attributes of this object!
Test code
I wrote some small demos for testing code, github: https://github.com/lihei12345/JSCoreDemo
Reference