preface

How JS in Web pages interact with iOS Native is a skill that every iOS ape must master. When it comes to Native and JS interaction, we have to mention Hybrid.

The translation result of Hybrid is not very civilized (Zihan, I don’t know why many translation software will translate it as “Hybrid”, but I prefer to translate it as “Hybrid”). In my understanding of Hybrid Mobile App, it is through Web network technology (such as HTML, CSS and JavaScript) with Native hybrid mobile applications.

So let’s take a look at the advantages and disadvantages of Hybrid versus Native:

Because of Hybrid’s flexibility (you don’t have to re-issue a Web page to change it) and versatility (you can play the H5 on all platforms in one copy), plus low barriers (the front-end ape can masturbate painlessly), Therefore, non-core functional modules realized by Hybrid using Web may be superior to Native in all aspects. Native, on the other hand, can provide strong support for JS in core functions and the invocation of device hardware.

The index

  • A brief history of Hybrid
  • Introduction of JavaScriptCore
  • IOS Native and JS interaction method
  • WKWebView and JS interaction unique method
  • JS calls the camera Demo of iOS devices through Native
  • conclusion

A brief history of Hybrid

Here’s a brief history of Hybrid:

1. The H5

Html5 was officially released in September 2014, and one of the biggest changes made this time around was “upgrading from a subset of XML to a stand-alone collection.”

2.H5 penetrates into Mobile App development

Native APP development has a WebView component (Android is WebView, iOS has UIWebview and WKWebview), this component can load Html files.

Before THE popularity of H5, web pages loaded by WebView were very monotonous (because they could only load some static resources). Since the popularity of H5, H5 pages developed by front-end apes performed well in webview, making H5 development slowly infiltrate into Mobile App development.

3. The present situation of Hybrid

Although RN and Weex have emerged to write Native apps using JS, Hybrid is still not eliminated, and most applications in the market introduce Web pages to varying degrees.

JavaScriptCore

JavaScriptCore is a library that Apple added into the standard library after iOS 7. It has an epoch-making impact on interactive calls between iOS Native and JS.

JavaScriptCore consists of four classes and one protocol:

  • JSContext is the JS execution context, which you can think of as the JS execution environment.
  • JSValue is a reference to a JavaScript value, and any value in JS can be wrapped as a JSValue.
  • JSManagedValue is a wrapper for JSValue with conditional retain.
  • JSVirtualMachine represents a separate environment for JavaScript execution.

There is also the JSExport protocol:

Implements a protocol for exporting Objective-C classes and their instance methods, class methods, and properties as JavaScript code.

JSContext JSValue JSManagedValue JSManagedValue JSContext JSValue

Usage of JSVirtualMachine and its relationship to JSContext

Introduction to official documents:

The JSVirtualMachine instance represents a separate environment for JavaScript execution. You use this class for two main purposes: to support concurrent JavaScript execution, and to manage memory for objects that are bridged between JavaScript and Objective-C or Swift.

About the use of JSVirtualMachine, under normal circumstances we do not need to manually create JSVirtualMachine. Because when we get the JSContext, we get the JSContext that belongs to a JSVirtualMachine.

Each JavaScript context (JSContext object) belongs to a JSVirtualMachine. Each JSVirtualMachine can contain multiple contexts, allowing values (JSValue objects) to be passed between contexts. However, each JSVirtualMachine is different, meaning we cannot pass a value created in one JSVirtualMachine to a context in another.

The JavaScriptCore API is thread-safe — for example, we can create JSValue objects or run JS scripts from any thread — but all other threads trying to use the same JSVirtualMachine will block. To run JavaScript scripts on multiple threads simultaneously (concurrently), use a separate instance of JSVirtualMachine for each thread.

JSValue and JavaScript conversion table

OBJECTIVE-C JAVASCRIPT JSVALUE CONVERT JSVALUE CONSTRUCTOR
nil undefined valueWithUndefinedInContext
NSNull null valueWithNullInContext:
NSString string toString
NSNumber number, boolean toNumber

toBool

toDouble

toInt32

toUInt32
valueWithBool:inContext:

valueWithDouble:inContext:

valueWithInt32:inContext:

valueWithUInt32:inContext:
NSDictionary Object object toDictionary valueWithNewObjectInContext:
NSArray Array object toArray valueWithNewArrayInContext:
NSDate Date object toDate
NSBlock Function object
id Wrapper object toObject

toObjectOfClass:
valueWithObject:inContext:
Class Constructor object

IOS Native interacts with JS

For the interaction between iOS Native and JS, we can first divide it into two situations from the call direction:

  • JS call Native
  • Call Native JS

JS call Native

In fact, JS calls tO iOS Native can also be implemented in two ways:

  • False Request method
  • JavaScriptCore method

False Request method

Principle: In fact, this way is the use of webview proxy method, in the webview began request interception request, judge whether the request is agreed false request. If it is a false request, it means that JS wants to call our Native method according to the convention. It is good to execute our Native code according to the convention.

UIWebView

The UIWebView proxy has a function for intercepting requests, just make a judgment in it:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL *url = request.URL;
    // Compare with the convention function name
    if ([[url scheme] isEqualToString:@"your_func_name"]) {
        // just do it}}Copy the code
WKWebView

WKWebView has two agents, WKNavigationDelegate and WKUIDelegate. WKUIDelegate will be covered in the next section, where we need to set and implement its WKNavigationDelegate method:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void(^) (WKNavigationActionPolicy))decisionHandler {
    NSURL *url = navigationAction.request.URL;
    // Compare with the convention function name
    if ([[url scheme] isEqualToString:@"your_func_name"]) {
        // just do it
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    
    decisionHandler(WKNavigationActionPolicyAllow);
}
Copy the code

Note: decisionHandler is the block of code to call when your application decides whether to allow or disable navigation. This code block takes a single parameter, which must be one of the constants of the enumerated type WKNavigationActionPolicy. Failure to call decisionHandler causes a crash.

Here is the JS code:

function callNative() {
    loadURL("your_func_name://xxx");
}   
Copy the code

Then use the button tag:

<button type="button" onclick="callNative()">Call Native! </button>Copy the code

JavaScriptCore method

IOS 7 has JavaScriptCore for Native JS interaction. We can obtain the JSContext after the webView is loaded, and then use the JSContext to reference the object in JS to explain or respond to it with Native code:

// First introduce the JavaScriptCore library
#import <JavaScriptCore/JavaScriptCore.h>

// Then in UIWebView's finished loading proxy method
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // Get the JS context
    jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    // Make references to elements in JS, such as methods can be interpreted as blocks, objects can also point to OC Native objects
    jsContext[@"iosDelegate"] = self;
    jsContext[@"yourFuncName"] = ^ (id parameter){
        // Note that the default thread is the thread for web processing. If the main thread operation is involved, you need to manually go to the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
        // your code}); }}Copy the code

The JS code is simpler. Simply declare an unexplained function (with a specified name), which is used to reference Native:

var parameter = xxx;
yourFuncName(parameter);
Copy the code

IOS Native calls JS

Javascript implementation methods for iOS Native calls are also divided by JavaScriptCore:

  • The WebView directly injects JS and executes
  • JavaScriptCore method

The WebView directly injects JS and executes

On iOS, webViews have apis for injecting and executing JS.

UIWebView

UIWebView has methods for injecting JS directly:

NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')".@"alert msg"];
[_webView stringByEvaluatingJavaScriptFromString:jsStr];
Copy the code

Note: This method returns the result of running JS (nullable NSString *). It is a synchronous method that blocks the current thread! Although this method is not deprecated, the best practice is to use evaluateJavaScript: completionHandler: Method of the WKWebView class.

The official document: The stringByEvaluatingJavaScriptFromString: method waits synchronously for JavaScript evaluation to complete. If you load web content whose JavaScript code you have not vetted, invoking this method could hang your app. Best practice is to adopt the WKWebView class and use its evaluateJavaScript:completionHandler: method instead.

WKWebView

Unlike UIWebView, WKWebView injects and executes JS methods without blocking the current thread. Considering that the JS code in the Web content loaded by the WebView may not be validated, blocking threads may suspend the App.

NSString *jsStr = [NSString stringWithFormat:@"setLocation('%@')".Xx, Nafu Hutong, Nanluogu Lane, Dongcheng District, Beijing];
[_webview evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
    NSLog(@ % @, % @ "", result, error);
}];
Copy the code

Note: methods do not block threads, and their callback blocks always run in the main thread.

Official documents: Evaluates a JavaScript string. The method sends the result of the script evaluation (or an error) to the completion handler. The completion handler always runs on the main thread.

JavaScriptCore method

The JSValue class is provided by the JavaScriptCore library.

A JSValue instance is a reference to a JavaScript value. You can use the JSValue class to convert basic values (such as numbers and strings) between JavaScript and Objective-C or Swift to pass data between native and JavaScript code.

However, as you can see from the OC and JS conversion tables I posted above, they are not restricted to the base values stated in the official documentation at all. If you are not familiar with JS, JSValue can also refer to objects and functions in JS, because the JS language does not distinguish between base values and objects and functions. In JS, “everything is an object”.

Show code:

// First introduce the JavaScriptCore library
#import <JavaScriptCore/JavaScriptCore.h>

// Get the JS context first
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// If UI operations are involved, cut back to the main thread and call YourFuncName in the JS code, using the @[parameter] array
dispatch_async(dispatch_get_main_queue(), ^{
    JSValue *jsValue = self.jsContext[@"YourFuncName"];
    [jsValue callWithArguments:@[parameter]];
});
Copy the code

The above code calls the YourFuncName function in the JS code and adds @[parameter] as an input parameter to the function. For easy reading, here is the JS code:

function YourFuncName(arguments){
    var result = arguments;
    // do what u want to do
}
Copy the code

WKWebView and JS interaction unique method

The difference between WKWebView and UIWebView is not explained in detail in this article. Please refer to it for more information. Here is how WKWebView interacts with JS:

  • WKUIDelegate method
  • MessageHandler method

WKUIDelegate method

WKWebView as mentioned above, in addition to WKNavigationDelegate, it also has a WKUIDelegate. What is the WKUIDelegate used for?

The WKUIDelegate protocol contains functions that listen when a Web JS wants to display an alert or confirm. If we load a Web in WKWebView and want the Web JS alert or confirm to pop up properly, we need to implement the corresponding proxy method.

Note: If no proxy method is implemented, the WebView will behave as the default.

  • Alert: If you do not implement this method, the web view will behave as if the user selected the OK button.
  • Confirm: If you do not implement this method, the web view will behave as if the user selected the Cancel button.

Here we take Alert as an example, and I believe readers can draw their own conclusions. WKUIDelegate: Native UIAlertController: Native UIAlertController: Native UIAlertController

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void(^) (void))completionHandler {
    // Use Native UIAlertController popup to display the information that JS will prompt
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@ "reminder" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@" Got it" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        // completionHandler must be called inside the function
        completionHandler();
    }]];
    
    [self presentViewController:alert animated:YES completion:nil];
}
Copy the code

MessageHandler method

MessageHandler is another JS method to invoke Native after Native intercepts JS false request. This method makes use of the new features of WKWebView. Compared with the method of intercepting fake Request, MessageHandler is more simple and convenient to transmit parameters.

What does MessageHandler mean?

The WKUserContentController class has a method:

- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
Copy the code

This method is used to add a script processor, which can process the method called by JS script in the processor, so as to achieve the purpose of JS calling Native.

What does the WKUserContentController class have to do with WKWebView?

In the WKWebView initialization function there is an input parameter configuration, which is of type WKWebViewConfiguration. WKWebViewConfiguration contains a property called userContentController. This userContentController is an instance of the WKUserContentController type, We can use this userContentController to add script handlers with different names.

The pit of MessageHandler

Then back to the – (void) addScriptMessageHandler: name: above method, this method is to add a message handler script (the first into the scriptMessageHandler), and gave a name to this processor (second name). However, there is a pitfall in using this function: The scriptMessageHandler entry is strongly referenced, so if you take the UIViewController of the current WKWebView as the first entry, The viewController is held by its own webView.Configuration. UserContentController, which creates a circular reference.

We can through the – (void) removeScriptMessageHandlerForName: method delete userContentController viewController strong reference. So normally our code will add and remove MessageHandler in pairs of viewWillAppear and viewWillDisappear:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.webview.configuration.userContentController addScriptMessageHandler:self name:@"YourFuncName"];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.webview.configuration.userContentController removeScriptMessageHandlerForName:@"YourFuncName"];
}
Copy the code
WKScriptMessageHandler agreement

WKScriptMessageHandler is a script information handler protocol that must be followed if you want an object to have script information handling capabilities (for example, the viewController of the WebView (self) above).

The WKScriptMessageHandler protocol is very simple internally, with only one method that we must implement (@required) :

// the WKScriptMessageHandler protocol method is triggered when a script message is received
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    // Message has two attributes: name and body
    Message. name can be used to distinguish the processing to be done
    if ([message.name isEqualToString:@"YourFuncName"]) {
        // message.body is the argument passed by JS
        NSLog(@"JS call native success %@", message.body); }}Copy the code

Add JS code:

// 
      
        replace YourFuncName, 
       
         replace your desired entry
       
      
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
Copy the code

Wrap it up!

JS calls the camera Demo of iOS devices through Native

A Demo was carried out by hand, which realized the interaction between JS and Native code, and realized the function of calling the camera of iOS device with JS in webView. Demo contains permission application, users refuse authorization and other details (technically, JS and Native mutual transfer value call), please advise.

Bow my head to all the gay men and offer my knees.

conclusion

  • This article gives a brief introduction to the Hybrid Mobile App (including a brief history of the development of Hybrid).
  • The composition of JavaScriptCore is introduced. And the relationship between JSVirtualMachine and JSContext and JSValue is expressed in the form of pictures (JSVirtualMachine contains JSContext contains JSValue, both are 1 to N relations, And because the code under the same JSVirtualMachine will block each other, so if you want to execute the interaction asynchronously, you need to declare the JSVirtualMachine on different threads to execute concurrently.
  • From the point of view of calling direction, the ways and methods of JS and iOS Native calling each other are explained with code examples.
  • Introduced WKWebView and JS interaction unique methods: WKUIDelegate and MessageHandler.
  • Provides a JS to call the camera of iOS devices through Native Demo.

End scatter flower, hope my article can bring value for you!


Supplement ~ I set up a technical exchange wechat group, want to know more friends in it! If you have any questions about the article or encounter some small problems in your work, you can find me or other friends in the group to communicate and discuss. Looking forward to your joining us

Emmmmm.. Because the number of wechat group is over 100, it is not possible to scan the code into the group, so please scan the qr code above to pay attention to the public number into the group.