JavaScriptCore-feature.png

JavaScriptCore is a very old API, which is used in MAC applications. It is pure c code, and in iOS7, it is wrapped in objective-c. JavaScriptCore allows us to execute JavaScript code without UIWebView and WKWebView. As a scripting language for Web development, JavaScript plays a very important role in cross-platform applications. Many cross-platform solutions, such as JSPatch and React-Native, are implemented based on JavaScriptCore for iOS.

JavaScriptCore

First, we need to know what’s in the JavaScriptCore library. In fact, this header file just imports 5 header files

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"Copy the code

The five classes are briefly summarized as follows

  1. JSContextIs the running context of JavaScript and the execution environment for executing JavaScript codes and registering native method interfaces.
  2. JSValueisJSContextThe result returned after execution can be any JavaScript type,JSValueProvides many methods for converting JavaScript and Objective-C types. But there’s a caveatJSValueIs a strong reference type, and JavaScript internally manages memory through a garbage collection mechanism. JavaScript and Objective-C type conversions are availableJSValueMethods provided.
  3. JSManagedValueisJSValueIt can solve the problem of cyclic reference between JavaScript and native code.
  4. JSVirtualMachineManages the JavaScript runtime and manages the memory of native objects exposed by JavaScript. You don’t normally need to talk to this class directly. But if you need to execute JavaScript code in parallel,JSContextI need to run in a differentJSVirtualMachineAbove the law. eachJSVirtualMachineHave one’s own
  5. JSExportIs a protocol implemented to expose a native object to JavaScript.

A diagram is referenced to illustrate some of the relationships of the above types





javascriptcore-700×310.png

Objective – C using JavaScript

JavaScript has no concept of classes and can be inherited through archetypal inheritance. Objective-c can call JavaScript functions and JavaScript properties. These functions and attributes need to be loaded into the JSContext as an NSString, and on success, a JSValue object is returned, if any. Normally, JavaScript files are stored locally, packaged into your program, or you can get JavaScript files from the web, and load them into the context, so that’s what we call hot and new.

Functions and Properties

The following JavaScript code exists

//jscore.js
var jsGlobleVar = "JS Demo";
function min(a,b){
    return a-b;
};Copy the code

So this min method can be retrieved in Objective-C as something like key-value, which is a JSValue, and then call callWithArguments to JSValue instance and pass in the arguments of objective-C type, The corresponding type conversion is performed in the JavaScript environment

//JSCoreViewController.m
  NSString *jsCode = [self readFile]; // jscore.js
    [self.context evaluateScript:jsCode]; / / the aboveminThe function is loaded into the context JSValue *min = [self.context[@"min"] callWithArguments:@[@2The @4]]; // self.context[@"min"] corresponds to jsminFunction orminAttribute JSValue *jsVar =self.context[@"jsGlobleVar"];
     NSLog(@"jsGlobleVar-----%@",[jsVar toString]); //"JS Demo"
    NSLog(@"min+++++%d"[min toInt32]); //-2Copy the code

JavaScript call Objective – C

JavaScript calls objective-C blocks

The following code registers a JSValue object with key multi to context

  self.context[@"multi"] = ^ (NSInteger a,NSInteger b){
        return a*b;
    };Copy the code

Rather, define a function in JavaScript that looks like this

        function multi (a,b){
             return a*b;
        }Copy the code

So the way that you’re calling this block in context is the same way that you’re calling JavaScript in Objective-C

  JSValue *multi  = [self.context[@"multi"] callWithArguments:@ [@ 3.@ 4]].//Or multi = [self.context evaluateScript:@"Multi (1, 2)"];Copy the code

Custom types and methods

In addition to exposing JavaScript objects using the JSContext subscript method, you can also use the JSExprot protocol to convert custom classes in Objective-C to JsValues, And expose it to a JavaScript object, and manipulate that Objective-C object in a JavaScript environment. First you need to define a subprotocol that follows JSExport. The subprotocol specifies which properties and methods are available in the JavaScript environment. Since function arguments have no types in JavaScript, all objective-c methods are called with a camel name, such as -(void)minuse:(NSInteger)a b:(NSInteger)b C :(NSInteger) C; Will be called as minuseBC(a,b,c). //JSExportAs(add, – (NSInteger)add:(NSInteger)a b:(NSInteger)b) To summarize, the main functions of JSExport are

  1. Pass objective-C functions and properties to JavaScript
  2. @ the property – > JavaScript getter/setter
  3. Objective-c instance methods –> JavaScript functions Objective-c
  4. Objective-c class methods —-> JavaScript functions on global class objects

Objective – C objects

Define the following Objective-C protocol and write a class that follows it (defined here using JSExportModel)

@protocol JSModelProtocol <JSExport>- (void)minuse:(NSInteger)a b:(NSInteger)b c:(NSInteger)c;
@property (assign.nonatomic)NSInteger sum;
@endCopy the code

You can then add an instance object of the protocol-compliant JSExportModel class to the execution environment and call the useOCObject method that has been loaded into the context in JavaScript.

- (void)excuteOCCdoeInJS{
    JSExportModel *model = [JSExportModel new];
    self.context[@"model"] = model;
    [self.context[@"useOCObject"] callWithArguments:nil];
    NSLog(@"%ld", (long)model.intV);/ / 14
}Copy the code

The JavaScript code for useOCObject is as follows

function useOCObject(){
    model.minuseBC(100.12.12);
    model.intV = 14;
};Copy the code

This calls objective-C object methods and modifies the attributes of the object passed in.

Pass in a class object

Since JavaScript has no concept of classes, all objective-C classes passed into the JavaScript environment become a Constructor object. If you need to generate an object in the JavaScript environment, You need to make this Objective-C class have a class method to generate instance objects, so in the previous protocol, we defined a class method to generate instance objects

// Declare in the protocol to expose toJavaScript
+(instancetype)createWithIntV:(NSInteger)value;Copy the code

Then make the following call in Objective-C

- (void)excuteOCClassInJS{
    self.context[@"Model"] = [JSExportModel class];
   JSValue *returned = [self.context[@"useOCClass"] callWithArguments:nil];
   JSExportModel *m= [returned toObjectOfClass:[JSExportModel class]].NSLog(@"%ld", (long)m.intV); / / 12

}Copy the code

The JavaScript code is shown below

function useOCClass(a){
    var m =  Model.createWithIntV(12);
    m.minuseBC(10.1.1); // Call objective-C methods
    return m
}Copy the code

As you can see, the objective-C code above prints out 12.

Returns a JavaScript function

Functions are also variables in JavaScript, first-class citizens, and can also be returned. There is a JavaScriptFunc function in the JavaScript file, as shown below

function callback (){
    // What is printed here can be opened in safari's development options
console.log("method----");
};
function jsFunc(){
    Obj.jsValue = callback // Assign directly to the variable
    return callback;  // Return function as callback
}Copy the code

This function returns the callback function to an Objective-C object. The first method above assigns a function object directly to an Objective-C object, and the second method returns the function object directly. You can call a function in Objective-C as follows.

- (void)jsReturnBlock{
     self.obj.jsValue = [self.context[@"jsFunc"] callWithArguments:nil];
    [ self.obj.jsValue callWithArguments:nil];

    self.context[@"Obj"] = self.obj;
    [self.context[@"jsFunc"] callWithArguments:nil];
    [ self.obj.jsValue callWithArguments:nil];
}Copy the code

Memory management

We all know that Objective-C uses ARC for memory management, that JavaScriptCore (virtualMechine) internally manages memory through garbage collection, and that all references are strong references. Internally, JavaScriptCore ensures that most of the memory management is automatic and does not require additional memory management. But there are two cases where you have to pay attention to memory management

  1. Store JavaScript values in Objective-C objects
  2. Add JavaScript areas (mainly functions) to Objective-C objects
    self.jsValue = [JSValue new];
     self.context[@"block"] = ^ () {JSValue *value = self.jsValue;
         NSLog(@"% @",value);
     };Copy the code

    Context refers to self, and self holds the context. In this case, the compiler will actually tell you that a circular reference has been created. The best way to do this is to pass the jsValue as a block parameter to the JavaScript environment.

If you want to use another JS object or function in a block, you need to retrieve the current JSContext. This creates a circular reference. The recommended method is +[JSContext currentContext] to retrieve the currentContext, i.e

self.jsValue = [JSValue new];
    self.context[@"block"] = ^ () {// Circular reference
     // context = self.context;
        JSContext *context = [JSContext currentContext];
        NSLog(@ "% @",value);
    };Copy the code

In another case where you must use an Objective-C object to hold a JavaScript value or function, you must use JSManagedValue to weakly reference the JavaScript object. JSManagedValue is itself an object that weakly references a JavaScript object. – addManagedReference: withOwner: JSManagedValue object added to the garbage collection reference. This function says that JavaScript’s garbage collection mechanism looks at references to objective-C object ower, and if it exists, it doesn’t recycle the object, otherwise it does.

thread

JSVirtualMachine ensures thread-safe JavaScript code execution and locks are controlled by JSVirtualMachine. Use different JSVirtualmachines for serial or concurrent implementation.

JavaScriptCore and UIWebView

You can call JavaScript code in a UIWebView, and it’s essentially UIWebView executing JavaScript code on its internal JSContext. You can call it after the UIWebView has loaded, and get this _context.

- (void)webViewDidFinishLoad:(UIWebView *)webView{
     _context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    [_context evaluateScript:@"showWebViewAlert()"];

}Copy the code

Online have said documentView. WebView. MainFrame. JavaScriptContext is private API, there is a risk of being rejected, need to be careful.

JavaScriptCore and WKWebView

The communication between WKWebView and JavaScriptCore can be realized by WKWebView’s proxy. In the js code By sending the following Message window. Its. MessageHandlers. AppModel. PostMessage ({body: ‘call js alert in js’}); WKWebView only needs to bind AppModel at startup time to receive JS messages. Specific as follows

WKUserContentController *contentVC = [WKUserContentController new];
    // Then we can use the proxy to fetch the message sent by js
    [contentVC addScriptMessageHandler:self name:@"AppModel"];
    config.userContentController = contentVC;Copy the code

This allows WKScriptMessageHandler to receive the message

// MARK: - WKScriptMessageHandler
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    // Received the message
     // window.webkit.messageHandlers.AppModel.postMessage({body: 'call js alert in js'});
    NSLog(@"% @",message.body);
}Copy the code

demo

Demo download address iOS using JS demo, like the star about it. Ha ha

The resources

Java​Script​Core

JavaScriptCore by Example

JavaScriptCore and iOS 7

JavaScriptCore Tutorial for iOS: Getting Started

Integrating JavaScript into Native Apps