Writing in the front

This article is a collation of my group share, most of the pictures are directly from the keynote screenshots, there are a lot of cool effects, if you read the blog, it all depends on imagination, multi-picture warning 🙂


An overview of

  • Introduction of JavaScriptCore
  • Objective-c interacts with JavaScript
  • JavaScript interacts with Objective-C
  • Memory management
  • multithreading

Introduction to JavaScriptCore

1.1 JavaScriptCore and JavaScriptCore framework

The first step is to distinguish between JavaScriptCore and JavaScriptCore frameworks (same as JSCore below)

The JavaScriptCore framework is a framework introduced by apple in iOS7 that makes direct interaction between objective-c and JavaScript code much easier.

JavaScriptCore is the JavaScript engine of Apple’s Safari browser. Perhaps you have heard of Google’s V8 engine. At WWDC, Apple demonstrated the latest Safari, and it is said that JavaScript processing speed is much faster than Google’s Chrome. This means that JavaScriptCore is as good as V8 in terms of performance.

JavaScriptCore framework is actually based on webKit in C/C++ implementation of a package of JavaScriptCore, in the old version of iOS development, many developers will also introduce WebKit libraries into the project compilation use. Now iOS7 uses it as a standard library.

The JavaScriptCore framework has been around for a long time in OS X, but the interfaces are pure C, and apple didn’t open the framework in iOS before iOS7. Therefore, many people who need to process JavaScript in iOS app have to compile javascriptCore. a from the open source WebKit, and the interface is pure C language. Perhaps apple saw that more and more applications were using self-compiled JavaScriptCore, so it took the easy way out and opened up the JavaScriptCore framework, along with an Objective-C wrapper interface.

This article will discuss the Objective-C wrapped JavaScriptCore framework that we use to develop iOS apps.

1.2 JavaScriptCore API Goals

Apple’s Objective-C wrapped JavaScriptCore interface has three goals:





JavaScriptCore API Goals

  • Automatic: Apple does a lot of things for us when using these apis, such as switching between OC and JS in many cases.

  • Safe: We all know that JS is a dynamically typed language, that is to say that you from JS the value can be any value passed to the OC, the OC is statically typed language, it can’t receive the various types of value of the dynamic, but you can literally, program will not collapse, apple hope these API is not easy to get wrong, and even make a mistake, It’s not going to crash, and it’s not going to crash. The other thing is that these apis themselves are thread-safe, which we’ll talk about later.

  • High fidelity: the front two better understanding, but the high is what explains, is very simple, is apple wants us to use these apis with JS interaction, writing the OC as in written OC, writing the JS like write JS, don’t need some strange grammar, we later examples.

Objective-c interaction with JavaScript





First of all, we introduce the JavaScriptCore framework, then create an object called JSContext, and then use this JSContext to execute a section of JS code 2 + 2, where the JS code is passed in as a string. The result is a value of type JSValue. Finally, the value of type JSVlaue is converted to an integer and printed.

The output is as follows, so we call a JS code with OC, easy right 🙂





There are two classes in this demo that WE haven’t seen before, one called JSContext and one called JSValue, so let’s go through them one by one.

2.1 JSContext
  • JSContext is the execution environment of JS code. JSContext provides the context environment for the execution of JS code. All JS codes executed by jSCore must be executed by JSContext.

  • JSContext corresponds to a GlobalObject in JS, which is the equivalent of a window object in a browser, and JSContext has a GlobalObject property, which is actually where all the JS code is executed, But for ease of understanding, JSContext can be equivalent to a global object.

You can imagine him like this:





JSContext

2.2 JSValue
  • JSValue, as the name implies, is the JS value, but the JS value can not be directly used in OC, it needs to be wrapped, this JSValue is the packaging of JS value, one JSValue corresponds to one JS value, The JS value can be a number, Boolean, object, function, undefined, or null. The diagram below:





    Comparison table of OC-JS types

    In fact, it is equivalent to var in JS.

  • A JSValue exists in a JSContext. A JSValue cannot exist independently, it must exist in a JSContext. Just as all elements in a browser are contained in a Window object, a JSContext can contain multiple Jsvalues. Something like this:





    JSValue

The lambda symbol in the figure stands for anonymous function, closure, and is capitalized ^, which is why Block definitions in OC all have a lambda symbol.

  • JSValue is strongly referenced both to its corresponding JS value and to the JSContext object to which it belongs. Because jSValue needs these two things to execute JS code, jSValue will always hold them.

The following diagram can more intuitively describe the relationship between them:





To create a JSValue object, do the following:





You can convert the OC types into their JS counterparts (see the type comparison table above) and package them in a JSValue, including the base types, Null and undpay.

Or you can create a new object, Array, regular expression, error, which is equivalent to saying var a = new Array() in JS;

It is also possible to convert an OC object into a JS object, but the attributes and methods in the converted object are not available in JS. How to get the OC object attributes and methods in JS, we will talk later.

2.3 Actual Use

A simple recursive function that computs factorial:





Demo_JS

Then, what if we want to call this JS function in OC? As follows:





Demo_OC

First, load the JS code from the bundle.

This is equivalent to declaring a function called fatorial in a global object, but not calling it, just declaring it, so there is no return value after executing this JS code.

We use a dictionary-like subscript to get the corresponding JS function. It is as simple as getting the value of the key in a dictionary. In fact, the JS object is given the key: The form of Value stores attributes, and the object type in JS corresponds to the dictionary type in OC, so this writing method is natural and reasonable.

This dictionary-like subscript can not only take values, but also store values. JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue = JSValue

Call callWithArguments. This method takes an array of arguments. This is because function arguments in JS are not fixed. JS does not know what NSNumber is, but don’t worry, JSCore will help us automatically convert the corresponding JS type. In this case, NSNumber type 5 will be converted to JS number type 5. This function is then called (this is where the automation in the API goal comes in).

Finally, if the function returns a value, the return value of the function is returned, if there is no return value, undefined. Of course, after passing through JSCore, the types in the JS are wrapped as JSValue. Finally, we get the returned JSValue object, convert it to the corresponding type and output it. This is 120, so I’m not going to post it.

JavaScript interaction with Objective-C

JavaScript interacts with Objective-C in two main ways:

  • Block: The first way is to use blocks, which can also be called closures and anonymous functions. Using blocks makes it very easy to expose individual OC methods to JS calls.
  • JSExport agreementThe second way is to useJSExportProtocol, which can expose an object in OC to JS directly, and use it in JS as naturally as the object calling JS.

In short, blocks are used to expose a single method, whereas the JSExport protocol can expose an OC object. We’ll look at both methods in detail below.

3.1 Block

JSCore will automatically wrap the Block as a JS method. If you want to do this, JSCore will automatically wrap the Block as a JS method. On the Demo:





So this is just a piece of code that exposes OC Block to JS, it’s very simple, isn’t it, just like this, we inject an OC Bock into the context using this dictionary-like notation that we mentioned earlier, and this Block takes an NSDictionary argument, And it returns an object of type NSColor.

So what happens when you write this? Please see below





We have a JSContext, and we inject an OCBlock into it, and JSCore automatically creates a function called makeNSColor in the global object (since the Context is assigned directly to the global object) that wraps the Block.

Then, in JS, we call the exposed block, actually calling the MakeNSColor method that encapsulates the block.









Here is a colorForWord JS method, which receives a word parameter. The colorMap is a JS object, which stores some color information by color name. These color information is also a JS object. The ColorForWord function retrieves the corresponding color object by the color name. We then call MakeNSColor and pass in a color object from the Word field in colorMap. Note that the color object is a JS object, but the Block we pass in receives an NSDIctionary parameter. Don’t worry, JSCore will automatically convert the JS Object type to NSDictionary, which corresponds to the JS Object type as described in the previous table.





Now we have a JS function makeNSColor that wraps the Block, and then we have a colorForWrod function that calls it like this:





From the left, colorForWrod calls makeNSColor. The parameter passed is JS Object (the color Object extracted from colorMap). JSCore converts this Object parameter to NSDictionary. MakeNSColor then calls the internally wrapped Block, which returns a value of type NSColor(NSObject), which JScore converts to a Wrapper Object(which is also a JS Object). Return to colorForWrod.

What would happen if we called the colorForWrod function in OC? The diagram below:





As the OC Caller calls the colorForWrod function, the colorForWrod function receives a String parameter word. As the OC Caller sends an NSString parameter, JSCore is converted to the corresponding String type. The colorForWrod function then calls down, as mentioned above, until it gets the Wrapper Object returned, it returns the Wrapper Object to the OC Caller that called it. JSCore converts the Wrapper Object to JSValue, and then calls the JSValue conversion method in OC to get the wrapper value. In this case, we call -toobject, and we get an NSColor Object. That is, the object returned from the original Block exposed to JS.

Through step by step analysis, we found that JavaScriptCore would perform the corresponding type conversion when transferring data at the junction of JS and OC, and the conversion rules are as shown in the previous OC-JS type comparison table.

3.1.1 Pits using blocks

Using the Block exposure method is convenient, but there are two pits to be aware of:

  • Do not use JSValue directly in blocks
  • Do not use JSContext directly in blocks

Because a Block strongly references external variables, if you use JSValue directly in a Block, the JSValue will be strongly referenced by the Block, and each JSValue will be strongly referenced by the JSContext to which it belongs, as we said earlier, The Block is injected into the Context, so the Block will be strongly referenced by the Context, which will cause circular references and memory leaks. For the same reason you can’t use JSContext directly.





For the first point, it is recommended to pass JSValue as an argument to a Block, rather than use it directly inside the Block, so that the Block does not strongly reference JSValue.

For the second point, you can use the [JSContext currentContext] method to get the currentContext.

3.2 JSExport agreement
3.2.1 introduction

Then there is the second way of INTERACTION between JS and OC: JSExport protocol. Through the JSExport protocol, objects in OC can be easily exposed to JS and used in JS as JS objects.

3.2.2 use

For example, we have a MyPoint class in Objective-C that has two properties of type double, x,y, an instance method description, and a class method makePointWithX: y:





If we use the JSExport protocol to expose this class object to JS, how do we use this exposed JS object in JS? His properties can be called directly, just like the properties of a JS object, his instance methods can be called directly, just like the methods in a JS object, and then his class methods can be called directly with some global object. Just like normal JS, but operating on an OC object.





All you need to do is write this sentence.

@protocol MyPointExports <JSExport>Copy the code

Declare a custom protocol and inherit from the JSExport protocol. Then, when you expose the object that implements the custom protocol to JS, JS can use OC objects just like native objects, which is the high fidelity of the API’s goal.





Note that the function declaration format in OC is not quite the same as that in JS. In the OC function, multiple arguments are declared with colon:, which obviously cannot be directly exposed to JS calls, which is not hi-fi.

When we expose an OC method with arguments to JS, JSCore will generate a corresponding JS function using the following two rules:

  • Remove all colons
  • Capitalize the first lowercase letter following a colon

For example, in the above class method, the method name should be makePointWithX:y: before the conversion, the corresponding method name generated in JS will be makePointWithXY.

Apple e knows that such inconsistencies can drive some obsessives to death. So we added a macro JSExportAs to handle this situation, which gives JSCore the name of the corresponding method in JS generated for the OC method.

For example, the method makePointWithX:y: can be written like this:





This makePoint is the name given to the JS method, so that the OC method makePointWithX:y: can be called directly from JS makePoint.

Note: This macro only works with OC methods that take arguments.

And then, there’s a little Demo that uses the JSExport protocol if you’re interested, it’s actually pretty easy to use.

3.2.3 inquiry

But what does the JSExoprt protocol actually do?

When you declare a custom protocol that inherits from JSExport, you are telling JSCore that the properties, instance methods, and class methods declared in the custom protocol need to be exposed to JS for use. (Methods not included in this protocol are not exposed.)

When you expose the object of the class that implements this protocol to JS, a corresponding JS object will be generated in JS. Then, JSCore will traverse the class that implements this protocol according to the content declared in the protocol, and convert the properties declared in the protocol to the properties in THE JS object. It essentially converts to getter and setter methods, and the conversion method is similar to the block method, creating a JS method that wraps the OC method, and then the instance method declared in the protocol converts to the instance method on the JS object, and the class method converts to the method on some global object in JS.





So what is this global object? This involves knowledge in JS:

  • Prototype & Constructor

    In traditional class-based languages such as Java and C++, the essence of inheritance is to extend an existing Class and generate a new Subclass. However, there is no class type in JS, so how to implement JS inheritance? The answer is through the Prototype object.

    JavaScript sets a prototype for each object it creates, pointing to its prototype object. Objects inherit properties and methods from their prototype objects.

    When we access an object’s property using obj.xxx, the JavaScript engine looks for the property on the current object. If not, it looks for the property on its prototype object. If not, it goes all the way back to the Object. prototype object. I have to return undefined.

    A prototype object is also an object and has a Constructor that creates the object.

    If we have a Student constructor and use it to create xiaoming, the prototype chain for xiaoming looks like this.

    function Student(name) { this.name = name; this.hello = function () { alert('Hello, ' + this.name + '! '); }} var new Student(' xiaoming ');Copy the code
    xiaoming ----> Student.prototype ----> Object.prototype ----> nullCopy the code

    In more detail, xiaohong is another object constructed by the Student function, and the red arrow is the prototype chain. Student.prototype (); student.prototype (); student.prototype (); student.prototype (); student.prototype ();

    In addition, Student has a prototype property that points to xiaoming and xiaohong. Xiaoming and xiaohong do not have prototype property, but you can use the nonstandard proto to check.

    So we say that xiaoming and xiaohong “inherit” from Student.

    Prototype: Student inherits from Object. Prototype: Student inherits from Object. The prototype Object points to null again.





    JS prototype chain

    Learn more about Prototype and Constructor here.

The constructor is a bit like the metaclass in OC, where all the class methods are in the metaclass, so the global object is the constructor in JS.

Here I draw a diagram to illustrate the correspondence between OC and JS when objects are exposed using the JSExport protocol:





We have an object point of MyPoint class. When we expose this OC object to JS using the JSExport protocol, JSCore will first generate a corresponding prototype object and constructor for this class in the JS context, and then JSCore will scan this class. Expose the contents declared in the JSExport protocol to JS, and properties (getters and setters) are added to the prototype object, while class methods are added to the constructor, in places that correspond to classes and metaclass in OC.

Then, as in the previous figure, the prototype object has a constructor property pointing to the constructor, and the constructor property points to the prototype object. We also know that MyPoint inherits from NSObject, and JSCore creates a prototype and constructor for the parent of the exposed class. The prototype of NSObject is the prototype of Object in JS.

Every Prototype object has a property called Prototype, capital P, which is a pointer to inheritance in JS. MyPoint points to NSObject. And the prototype Object for NSObject, and the prototype Object for Object will point to NULL. Finally, Mypoint class constructor and prototype object are used to generate a JS object corresponding to OC point object in JS.

This constructs the same class inheritance relationship in JS with the JS architecture as in OC.

This is the key to the fact that OC objects exposed using the JSExport protocol can be invoked in JS just like JS objects.

4. Memory management

As we all know, Objective-C uses ARC (Automatic Reference Counting), which cannot automatically solve the retain cycle problem. While JavaScript uses Garbage Collection (Tracing Garbage Collection), all references are strong references, but the Garbage collector can help me solve the problem of circular references. JavaScriptCore also uses Garbage Collection. Generally speaking, Most of the time we don’t need to manage memory manually.

However, the following two situations need to be noted:

  • When we expose an OC object to JS, we can manipulate the OC object just like we manipulate the JS object with JSExport protocol, but do not add member variables to the OC object in JS. The result of this action is that only one additional member variable will be added to the OC object in JS, but the OC will not be added synchronously. So it doesn’t make sense, and it can cause some weird memory management problems.

  • OC objects should not directly reference JSValue objects. Do not directly store an OC object as an attribute or a member variable in an OC object, especially if the OC object is also exposed to JS. This creates circular references. The diagram below:





How to solve this problem? You might think, well, if you can’t strong reference it, then weak reference it, like this, but that doesn’t work either, because JSValue doesn’t reference it with an object, and it gets freed.





So what? Analyzing this, we need a weak reference relationship here, because strong references create circular references, but we can’t let the JSValue be released if no one references it. In short, a weak reference keeps the JSValue from being released.

As a result, Apple opted out of a new reference relationship called Conditional Retain, a conditional strong reference that achieves the desired effect of our analysis, and JSManagedValue is the class apple uses to implement Conditional Retain.





4.1 JSManagedValue





This is the general procedure for using JSManagedValue:

  • First, create a JSManagedValue object with JSValue. The JSManagedValue object contains a JSValue object that can be accessed by a read-only value property. This step is to add a weak reference to the JSValue.

  • If there is only the first step, the JSValue will be released after the JSManagedValue is collected by the garbage collector. The effect is the same as a weak reference, so you need to add an Owner for the JSManagedValue object on the virtual machine (this virtual machine is used to provide resources for JS execution, but more on that later). Doing so adds a strong relation to the JSValue, and the JSValue contained in the JSManagedValue will not be freed if either of the following is true:

    • The JS value corresponding to JSValue is not collected by the garbage collector
    • The Owner object was not released

This avoids circular references and ensures that the JSValue is not immediately released due to weak references.

V. Multithreading

Before we talk about multithreading, let’s talk about another class, JSVirtualMachine, which provides the underlying resources for JavaScript to run, with its own separate stack and garbage collection mechanism.

JSVirtualMachine is also a container of JSContext, which can contain several JSContexts. In a process, you can have multiple JSVirtualMachines, which contain several JsContexts, which in turn have several JsValues. Their inclusion relationship is shown below:





Note that you can pass jsValues between jsContexts of the same JSVirtualMachine, but not between JsContexts of different JsVirtualmachines.





This is because each JSVirtualMachine has its own separate stack and garbage collector, and one JSVirtualMachine garbage collector doesn’t know what to do with values passed from another stack.

Back to multithreading, the apis provided by JavaScriptCore are inherently thread-safe.

You can create JSValue in different threads and execute JS statements using JSContext, but while one thread is executing JS statements, other threads must wait to use the JSVirtualMachine of the JSContext that is executing JS statements. The JSVirtualMachine cannot be used until the previous thread completes execution.

Of course, the granularity of the forced serialization is JSVirtualMachine. If you want to execute JS code concurrently in different threads, you can create different JSVirtualMachines for different threads.

One last thing, about how to get the JSContext in UIWebView, which I won’t go into because of space, is a recommended article.






reference

Integrating JavaScript into Native Apps JavaScriptCore API Reference