This article by our team tangled shoes.
Write in front
This article is for me a group share consolidation, most of the pictures are shot down directly from keynote, I have a lot of cool effect, see the blog of words depends on brain up, multi warning:)
- Automation: the use of these API, a lot of things are apple to help us do, such as the interaction between OC and JS, a lot of time will automatically help us to convert types.
- Safety: we all know that JS is a dynamically typed language, that is to say that you transfer from JS to OC in value may be of any value, while OC is a statically typed language, it can dynamically receive various types of value, but you can not pass, does not collapse. Apple hopes these API is not easy to go wrong, even if wrong, but also will not lead to the collapse of the program, it is true. Another thing is that API itself is thread safe, and we’ll talk about it later.
- High fidelity: the front two is better understood, but the high fidelity is how to explain it, very simple, is that Apple hopes that when we use these API to interact with JS, as in writing OC write OC, like writing JS write JS, do not need some strange words, this behind us with examples.
The results are as follows, so we use OC to call a JS code, very simple:
There are 2 demo before the class has not seen, one called JSContext, one called JSValue, the following we say a.
- JSContext is the execution environment of JS code
JSContext provides a context for the execution of JS code, the JS code executed by jSCore must be implemented by JSContext.
JSContext corresponding to a global object in JS corresponds to a global object that corresponds to the browser window object has a GlobalObject attribute in JSContext, the JS code is executed in the GlobalObject, but in order to understand easily, JSContext can be equivalent to the global object.
You can think of him like that:
- JSValue is the name of the JS value of the
JSValue package is the JS value. However, the value of the JS to OC is not directly with the needs of packaging, this is the JSValue value for JS packaging, a JSValue corresponds to a JS value, the JS value may be JS in number, the basic type Boolean, may also be the object, function, or even can be undefined or null. The following figure:
in fact, equivalent to JS in the var.
exists in JSContext JSValue is not independent existence, it must exist in a certain JSContext, all elements like the browser are included in the Window object, a JSContext can contain multiple JSValue. Like this:
The OC (lambda) symbol in the Tips: diagram represents an anonymous function, the meaning of the closure, and its capital form is ^, which is why the Block definition in the.
- is a strong reference to this point is critical, JSValue on its corresponding JS value and its subordinate JSContext objects are strongly cited relationship. Because jSValue needs these two things to execute the JS code, JSValue will hold them all the time.
The following figure can be more intuitive description of the relationship between them:
Create a JSValue object using the following methods:
you can convert the type in OC to the corresponding type in JS (see the previous type of comparison table) and wrap it in JSValue, including the basic types, Null and undfined.
Or you can create a new object, array, regular expression, error, these methods to achieve the effect is equivalent to writing in JS var = new Array ();
Can also be a OC object to the JS object, attribute and method but this conversion of the object, in the JS are not available, how to make JS properties and methods to obtain the OC object, behind us again.
2.3 actual use
Look at a Demo:
first is a section of the JS code, a simple recursive function, factorial calculation:
then what should we do if we want to call a function in the JS in OC? As follows:
first, load the JS code from bundle.
Then, create a JSContext and use it to execute the JS code, which is equivalent to the effect of a global object declared in a function called fatorial, but did not call it, just after the execution of this statement, so JS code does not return a value.
To get the function from the global object, JS function, here we use a dictionary like subscript notation to obtain the corresponding, as in a dictionary from the corresponding key value as simple, in fact, the JS object is to key storage properties of Value and JS in the form. The object object type, corresponding to the OC is a dictionary type, so this is natural and reasonable.
This dictionary like index can not only value, but also the value of. Not only can function in Context, also works with JSValue, he will fill in the brackets with key value to match the JSValue contains the values of JS have no corresponding attribute field, found on the back, did not find it returns undefined.
Then, we got the package the factorial function of the JSValue object, the callWithArguments method is called, you can call the function, the method of receiving an array as a parameter, this is because the parameters in the JS function is not fixed, we construct an array, and the NSNumber type 5 pass in the past, however, JS certainly did not know what NSNumber is, but don’t worry, JSCore will help us to automatically convert the corresponding type of JS, there will be NSNumber types 5 into number type JS in 5, and then to call this function (which is to say in front of the automation of the target in API).
Finally, if the function return value will function returns, if no return value is returned in the course undefined, after JSCore, these types of JS are packaged into JSValue, finally we get the JSValue object that is returned to the type and the corresponding output. The result here is 120, I’m not going to stick it out.
- Block: the first way is to use block, block can also be called closures and anonymous functions, the use of block can be very convenient to OC single method exposed to the JS call, the specific implementation of our later.
- JSExport protocol: the second way is to use the JSExport protocol, the OC can be directly exposed to the use of a JS object, but also in the use of JS as the object to call the same natural JS.
In short, Block is used to expose a single method, and the JSExport protocol can expose a OC object, we have to talk about these two ways.
As mentioned above, the use of Block can be very convenient to the OC in a single method (ie Block) exposed to the JS call, JSCore will automatically wrap the Block into a JS method, specifically how to package it? Upper Demo:
this is a OC Block exposed to JS code, is not very simple, like this, like this dictionary we use the wording of a OC Bock into the context, the block receives a parameter of type NSDictionary, and returns an object of type NSColor (NSColor is the class of APPkit, is used in the development of Mac, the equivalent of UIkit NSColor).
What will happen if you write like that? See figure
We have a JSContext, then a OCBlock into account, JSCore will automatically in the global object (because it is assigned directly in Context, context corresponding to the global object) to create a function called makeNSColor, the Block package.
Then, in the JS, we call this exposed block, in fact, is a direct call to the packaging of the MakeNSColor Block method.
There is a
called colorForWord JS, it receives a word parameter, the colorMap is a JS object, which holds the name according to the color value color information, the color information is one of the JS object, the ColorForWord function is to obtain the corresponding color object through the color name. Then this function calls the MakeNSColor method, and introduced from colorMap according to the word field out of the color object, pay attention to the color of the object is a JS object, is a type of object, but we came in the Block receiver is a parameter of type NSDIctionary ah, don’t worry, then JSCore will automatically help us get the JS object type to NSDictionary type, and the like in front of the written, NSDictionary corresponds to JS in Object type.
now, we have a wrapper Block JS function makeNSColor, and then there is a colorForWrod function to call it, the specific process is like this:
looked from the left, colorForWrod called makeNSColor, passed parameter is JS Object type (taken from the colorMap color object), JSCore will pass the Object parameter into NSDictionary type, and makeNSColor used to call the internal packaging Block, Block returns a NSColor return value (NSObject) the type, JScore will be transformed into a wrapper Object (actually is JS Object type), returned to the colorForWrod.
What would it look like if we called the colorForWrod function in OC? The following figure:
OC Caller to call the colorForWrod function, the function receives because colorForWrod is a String type of the parameter word, OC Caller passed is a parameter of type NSString, JSCore into the corresponding String type. Then the colorForWrod function to call down, as said above, know the return of wrapper Object to wrapper Object, it will return to call it OC Caller, JSCore and wrapper at this time to turn into a Object JSValue type conversion of the JSValue to call the corresponding method by then OC, you can get inside the packaging value here, we call the toObject method, the final will be a NSColor object, the object is from the beginning of return that exposure to JS in Block.
3.1.1 use Block pit
It is convenient to use the Block exposure method, but there are 2 pits need to pay attention to:
- Do not use Block directly in JSValue
- Do not use Block directly in JSContext
Because the Block strong reference external variables it is used inside, if you use the JSValue directly in the Block, then the JSvalue will be the Block strong references, and each JSValue is a strong reference that it belongs to JSContext, it is said, while the Block is injected into the Context so, the Block context will be a strong reference, this will result in a circular reference, causes a memory leak. Reasons for not using JSContext directly.
So how to do it, for the first point, it is recommended to use JSValue as a parameter to the Block, rather than directly in the Block internal use, so that Block will not be a strong reference to the JSValue.
For second points, you can use the [JSContext currentContext] method to obtain the current Context.
3.2 JSExport protocol
Then JS and OC interaction in second ways: JSExport protocol, through the JSExport protocol can be very convenient to expose the OC object to the use of JS, and in the JS and use it as the JS object.
For a chestnut, we have a MyPoint class in Objective-C, which has two properties of the double type, x, y, an instance method description and a class method makePointWithX: Y:
if we use the JSExport protocol to expose objects of this class to JS, how do we use the exposed JS object in JS? His property can be called directly, like the attribute JS object called the same, his example method can be called directly, like calling the JS object, and then his methods, can be directly used a global object directly call. Like ordinary JS, but the operation is a OC object.
to achieve these only need to write such a sentence.
@protocol MyPointExports < JSExport>
Declare a custom protocol and inherit from the JSExport protocol. Then when you expose the object of this custom protocol to JS, the JS can use the OC object like the native object, which is the high fidelity of the API object.
It should be noted that the OC function declaration format is not the same as in JS (it should be said that most of the language is not the same.) The OC function is a colon with a number of arguments: declared, which is obviously not directly exposed to the JS call, which is not high fidelity..
So we need to make some adjustments to the name of the method with parameters, when we expose a parameter with the OC method to JS, JSCore will use the following two rules to generate a corresponding JS function:
- Remove all colons
- The first lowercase letter followed by a colon
For example, the above class method, before the conversion method name should be makePointWithX:y:, JS generated in the corresponding method name will become makePointWithXY.
Apple is aware of this inconsistency may kill some obsessive-compulsive disorder.. So add a macro JSExportAs to deal with this situation, its role is: to JSCore in JS for the OC method generated by the corresponding method to specify the name.
For example, the above method or makePointWithX:y:, you can write:
this makePoint is the name given to the JS method, so that the JS can be called directly in the makePoint to call the OC method makePointWithX:y.
Note: this macro is valid only for the OC method with parameters.
Then, there is a JSExport agreement to use the small Demo are interested can look at, in fact, very simple to use.
However, the light will not be used, the JSExoprt agreement in the end what to do?
When you declare a custom protocol inherited from JSExport, you are telling JSCore that the attributes, instance methods, and class methods declared in this custom protocol need to be exposed to JS. The method that is not in this agreement will not be exposed
When you put the object that implements this protocol class of exposure to JS, JS will generate a corresponding JS object, then JSCore will be in accordance with the declaration of this agreement, to achieve this traversal category agreement, the agreement in the property declaration, into the properties of the JS object, is essentially convert getter and setter method, conversion method and said before block, create a JS package in the OC method, then the protocol statement instance methods into an instance method on the JS object, a global object on the JS conversion method.
What exactly is a global object that is said here? This involves knowledge in JS:
more about Prototype and Constructor knowledge can be seen here.
Don’t know that the prototype object here, a bit like OC in the class, and the constructor, is a bit like OC in the class, class OC is placed in the middle of a class, so that the global object is JS in the constructor.
Here I draw a diagram to describe the relationship between OC and JS when exposed to the JSExport protocol:
We have a MyPoint class object point, when we use the JSExport protocol to the OC object exposed to JS, and JSCore first prototype object constructor generates a corresponding to the class in the context of JS, then JSCore will scan this class, the Declaration on the JSExport agreement exposed to JS property, (getter and setter) will be added to the prototype object, and the method will be added to this constructor, this place, it corresponds to the OC class and metaclass.
Then, as in the previous diagram, the prototype object has a constructor property that points to the constructor, which has a prototype attribute that points to the prototype object. We also know that the MyPoint class is inherited with the NSObject class, JSCore will also be exposed to the class of the parent class to create a prototype object and constructor, NSObject class prototype object is the Object JS class prototype object.
Each prototype object has an attribute called Prototype, capital of P, he is a pointer used to express inheritance in JS (a prototype chain), prototype prototype object MyPoint will point to the NSObject class. The prototype object of NSObject, and the prototype object of Object will point to null. Finally, the Mypoint class constructor and the prototype object are used to generate a JS object corresponding to the point object in OC in JS.
In this way, the JS is used to construct the class inheritance relationship with OC in JS.
This is the key to the use of the JSExport protocol exposed OC objects in JS can be like calling JS objects.
Four. Memory management
But the following 2 situations need to pay attention to:
- Don’t give a OC object in the JS add member variables, the meaning of this sentence is to say, when we will be exposed to a OC object JS, as described above using the JSExport protocol, we can manipulate JS objects to manipulate OC objects, but this time, don’t let the OC in JS to add member variable, because the action of the consequences is that only in the JS to add an additional member variables for the OC object, but OC does not increase. So there is no point in doing so, there may be some strange memory management issues.
- OC objects do not directly strong reference to the JSValue object, this words say frankly, is not directly to an object of type JSValue as attributes or member variables stored in a OC object, especially the OC object also exposed to JS time. This causes a circular reference. The following figure:
How to solve this problem? You might think that you can’t use a strong reference, and that’s a weak reference, like this, but it’s not enough, because JSValue doesn’t use an object to refer to him, and he will be released.
What can we do? In this case, we need a weak reference relationship, because a strong reference causes a circular reference, but it is not allowed to be released without reference to the JSValue. In short, weak references can keep JSValue from being released.
So, Apple launched a new reference, called conditional retain, has a strong reference conditions, required to achieve our previous analysis can be referenced by this effect, and JSManagerValue is used to achieve the conditional Apple retain class.
this is the general use of JSManagerValue steps:
- First, a JSManagerValue object is created by JSValue JSManagerValue, which is actually a JSValue object through the value property of a read-only access to it, this step is to add a JSValue to the weak reference.
- If there is only the first step, the JSValue in the corresponding JS value after being released by the garbage collector, and this effect is like weak references, so also need to add a step in the virtual machine to add Owner to the JSManagerValue object (this virtual machine is to provide JS resources, will be more) and after doing so, to increase JSValue a strong relationship, as long as there is a little, the JSManagerValue which contains the JSValue will not be released: JSValue JS values are not garbage collector Owner object has not been released
To do so, that is, to avoid the cycle of reference, but also to ensure that the JSValue will not be released because of weak reference immediately.
A container of JSVirtualMachine or JSContext, can contain a number of JSContext, in a process, you can have multiple JSVirtualMachine, which contains a number of JSContext, JSContext and several JSValue, including their relationship as below:
It should be noted that, you can be in the same JSVirtualMachine different JSContext, mutual transmission of JSValue, but can no longer be different between the JSVirtualMachine JSContext JSValue.
this is because each JSVirtualMachine has its own separate stack and garbage collector, a JSVirtualMachine garbage collector does not know how to deal with the value from another stack.
You can create JSValue in different threads, and executing the JS statement with the JSContext, but when a thread is executing a JS statement, want to use the other thread is executing the JS statement JSContext the JSVirtualMachine will have to wait, wait before a thread is executed, in order to use this JSVirtualMachine.
Of course, this forced serial granularity is JSVirtualMachine, and if you want to execute JS code without thread, you can create different JSVirtualMachine for different threads.
Last but not least, it is about how to get the UIWebView in JSContext, due to the length of the problem here is not to repeat, recommend an article.
Java Core Reference