Interaction between JS and Native in iOS development

In iOS development, the interaction between JS and Native is divided into two types. The first one is Native calling JS, that is, by executing JS in Native code to achieve the effect of displaying corresponding JS code in webkit controls; the other is JS calling Native , through the execution of the front-end JS of the web to call the native local method to achieve effects such as opening the camera, data persistence, etc. that can only be achieved through the native code.

At present, there are two main ways to interact between JS and Native. The following will introduce them one by one:

1. WebView method/proxy method

Generally speaking, there are two main controls for loading web pages in iOS, UIWebView and WKWebview. The two controls have different implementation methods. We will introduce them separately here:

UIWebView control

  • Native calls JS:

Executing JS statements in Native is very simple. As a scripting language, its execution requires the existence of an interpreter, that is, a browser, so UIWebView, as a browser control, provides an object method for native to call JS:

//script 是要执行的 JS 语句
//返回值为 JS 执行结果,如果 JS 执行失败则返回 nil,如果 JS 执行没有返回值,则返回值为空字符串
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script; 

A demo is written here for reference only:

- (void)webViewDidFinishLoad:(UIWebView*)webView
{
    NSString* str = [self.webView stringByEvaluatingJavaScriptFromString:@"pageDidLoad()"]; NSLog(@"%@", str); } 

When the WebView is loaded, the pageDidLoadmethod , and the execution result of JS is printed on the console.

  • JS calls Native:
    Using the WebView method/proxy method to complete the JS call to Native is a little more complicated, and requires good cooperation between the Native front-end and the web front-end. The main principle is to intercept the jump request of the web front-end through the proxy method of UIWebVIew. The agreed custom protocol header is used to determine whether this request is a request for JS to call Native, and to call the corresponding Native method.
    The UIWebView proxy methods involved are:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

The following is an example to demonstrate:

JavaScript code:

function btnOnClickBaidu() {
        var url = "http://www.baidu.com"; alert("马上跳转的页面是:" + url); window.location.href = url; } function btnOnClickNative() { var url = "DZBridge://printSomeWords"; alert("马上跳转的页面是:" + url); window.location.href = url; } function btnOnClickNativeWithConfig() { var url = "DZBridge://printSomeWords?{\"string\":\"Hello World\"}"; alert("马上跳转的页面是:" + url); window.location.href = url; } function pageDidLoad() { alert("页面加载完毕!"); return 11; } 

OC code:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
    //dzbridge 为约定好的协议头,如果是,则页面不进行跳转 if ([request.URL.scheme isEqualToString:@"dzbridge"]) { //截取字符串来判断是否存在参数 NSArray<NSString*>* arr = [request.URL.absoluteString componentsSeparatedByString:@"?"]; if (arr.count > 1) { NSString* str = [arr[1] stringByRemovingPercentEncoding]; NSDictionary* dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL]; NSLog(@"%@", dict[@"string"]); } else { NSLog(@"没有参数的打印"); } return NO; } //不是自定义协议头,跳转页面 return YES; } 

WKWebView control

After iOS8, Apple launched a new framework WKWebKit, which provides a component WKWebView that can replace UIWebView. It turns out that the various problems of UIWebView have been improved, the speed is faster, and the memory is less (when the simulator loads Baidu and open source Chinese websites, WKWebView occupies 23M, while UIWebView occupies 85M). At present, WKWebView is the loading webpage inside the App. A better choice!
Compared with UIWebView, WKWebView has done a relatively large reconstruction, and refactored UIWebViewDelegate and UIWebView into 14 classes and 3 protocols. Therefore, the interaction between JS and Native in WKWebView is also quite different from UIWebView.

  • Native calls JS:
    The way that Native calls JS in WKWebView is similar to that in UIWebview, and it is also through an object method of its own:
// javaScriptString 为待执行的 JS 语句
// completionHandler 为执行 JS 完毕后的回调,block 的第一个参数为执行结果,第二个参数为错误
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler; 

See a small example below:

#pragma mark----- WKNavigationDelegate -----

- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{
    [self.webView evaluateJavaScript:@"pageDidLoad()" completionHandler:^(id _Nullable value, NSError* _Nullable error) { NSLog(@"%@", value); }]; } 
  • JS calls Native: JS calls Native
    in WKWebView is quite different from UIWebView. First of all, several classes (/protocols/properties) need to be introduced:
  1. WKWebViewConfiguration: is the configuration class when WKWebView is initialized, which stores a series of properties for initializing WK;
  2. WKUserContentController: A class that provides a channel for JS to send messages and can inject JS into the page;
  3. WKScriptMessageHandler: A protocol. There is only one method in the protocol. This method is a callback for the page to execute a specific JS. The specific JS format is: window.webkit.messageHandlers.<name>.postMessage(<messageBody>);

WKWebViewConfigurationAs the configuration class of WK, one of the properties is

@property (nonatomic, strong) WKUserContentController *userContentController;

WKUserContentControllerAn instance of yes, there WKUserContentControlleris an object method as:

/*! @abstract Adds a script message handler.
 @param scriptMessageHandler The message handler to add.
 @param name The name of the message handler.
 @discussion Adding a scriptMessageHandler adds a function
 window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
 frames.
 */
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name; 

From the comments given by Apple, this method can add a script message processor, that is (id <WKScriptMessageHandler>)scriptMessageHandler, it can also be found that after adding a script processor, it needs to be added in JS window.webkit.messageHandlers.<name>.postMessage(<messageBody>)to work.

demo:

// 创建并配置 WKWebView 的相关参数
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
WKUserContentController* userContent = [[WKUserContentController alloc] init]; // self 指代的对象需要遵守 WKScriptMessageHandler 协议 [userContent addScriptMessageHandler:self name:@"test"]; config.userContentController = userContent; 

When the JS on the page executes window.webkit.messageHandlers.<name>.postMessage(<messageBody>), the added method ScriptMessageHandlerexecutes the implemented WKScriptMessageHandlerprotocol, for example:

#pragma mark----- WKScriptMessageHandler -----
/**
 *  JS 调用 OC 时 webview 会调用此方法
 *
 *  @param userContentController  webview 中配置的 userContentController 信息
 *  @param message                js 执行传递的消息
 */
- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message { NSLog(@"%@", message); } 

Implementing the corresponding Native code in the proxy method completes the process of JS calling Native.

二、JavaScriptCore

OS X Mavericks and iOS 7 introduced the JavaScriptCore library, which wraps WebKit's JavaScript engine in Objective-C, providing a simple, fast and safe way to access JavaScript.

Classes and protocols in JavaScriptCore

  • JSContext: The context in which JavaScript runs
  • JSValue: A bridge between JavaScript and Objective-C data and methods
  • JSExport: This is a protocol. If the protocol method is used to interact, the protocol defined by yourself must abide by this protocol
  • JSManagedValue: class for managing data and methods
  • JSVirtualMachine: Handle thread related, use less

JavaScript calls Native

Using JavaScriptCore to interact between JS and Native, no matter what effect you want to achieve, you need to obtain a valid JSContext instance, that is, a valid JS running context (this step will not be repeated below).

  • Obtain the current JSContext:
    After the page is loaded, it can be obtained from the webView by means of KVC, as follows:
JSContext* jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
  • Abstract the method you want to be exposed to JS into a protocol, which needs to conform to the JSExportprotocol :
@protocol JSObjcDelegate <JSExport>
- (void)callCamera; - (NSString*)share:(NSString*)shareString; @end 
  • The class of the object that will be exposed to JS needs to comply with the custom protocol, as above: JSObjcDelegate;
  • Bridge OC objects into JS environment and set up exception handling
// 将本对象与 JS 中的 DZBridge 对象桥接在一起,在 JS 中 DZBridge 代表本对象
[self.jsContext setObject:self forKeyedSubscript:@"DZBridge"];
self.jsContext.exceptionHandler = ^(JSContext* context, JSValue* exceptionValue) { context.exception = exceptionValue; NSLog(@"异常信息:%@", exceptionValue); }; 
  • Call the methods exposed by this object through DZBridge in JS:
var callShare = function() {
        var shareInfo = JSON.stringify({"title": "标题", "desc": "内容", "shareUrl": "http://www.jianshu.com"}); var str = DZBridge.share(shareInfo); alert(str); } 

Native calls JavaScript

  • The first method is similar to UIWebView, which directly executes JS strings and executes JS code through JSContext:
[self.jsContext evaluateScript:@"alert(\"执行 JS\")"];
  • Another way is to execute the existing method on the web page, call the method in JS through JSValue, JSValue is a worthy reference in JavaScript, it may wrap a JavaScript method, and call it through the callWithArguments:method , for example:
JSValue* picCallback = self.jsContext[@"picCallback"];
[picCallback callWithArguments:@[ @"photos" ]];

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325163677&siteId=291194637