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
-
JSContext
Is the running context of JavaScript and the execution environment for executing JavaScript codes and registering native method interfaces. -
JSValue
isJSContext
The result returned after execution can be any JavaScript type,JSValue
Provides many methods for converting JavaScript and Objective-C types. But there’s a caveatJSValue
Is a strong reference type, and JavaScript internally manages memory through a garbage collection mechanism. JavaScript and Objective-C type conversions are availableJSValue
Methods provided. -
JSManagedValue
isJSValue
It can solve the problem of cyclic reference between JavaScript and native code. -
JSVirtualMachine
Manages 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,JSContext
I need to run in a differentJSVirtualMachine
Above the law. eachJSVirtualMachine
Have one’s own -
JSExport
Is a protocol implemented to expose a native object to JavaScript.
A diagram is referenced to illustrate some of the relationships of the above types
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
- Pass objective-C functions and properties to JavaScript
- @ the property – > JavaScript getter/setter
- Objective-c instance methods –> JavaScript functions Objective-c
- 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
- Store JavaScript values in Objective-C objects
- 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
JavaScriptCore
JavaScriptCore by Example
JavaScriptCore and iOS 7
JavaScriptCore Tutorial for iOS: Getting Started
Integrating JavaScript into Native Apps