https://www.jianshu.com/p/bb666b71e104

一、简述

目前原生与JS交互的方式有以下几种

JavaScriptCore

WKWebView

拦截URL

WebViewJavascriptBridge库

二、JavaScriptCore

(一)定义

1.JSContext

为JS的执行提供了上下文环境,通过JSCore执行的JS代码都要通过JSContext来执行。(上下文对象给两者的交互搭建了环境)

2.JSValue

是JS值在OC中的封装,以便JS值在OC中执行

3.两者关系

JSValue不可独立存在,必须依存于JSContext。一个JSContext可对应多个JSValue

每一个JSValue对象都要强引用关联一个JSContext。当与某JSContext对象关联的所有JSValue释放后,JSContext也会被释放。

(二)建立连接

1.文件头部引入相关库

#import <JavaScriptCore/JavaScriptCore.h>

2.加载本地HTML页面

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"JSInteraction" ofType:@"html"];
    NSString *htmlStr = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    [_webView loadHTMLString:htmlStr baseURL:[NSURL URLWithString:filePath]];

3.在webView的代理方法webViewDidFinishLoad中实现连接

//通过KVC方式从webView上获取相应的JSContext

self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

//设置错误处理函数

[self.context setExceptionHandler:^(JSContext *context, JSValue *exception) {

NSLog(@"oc catches the exception: %@",exception); }];

self.context[@"JsInteractive"] = self;

Pay:由于加载JS在webViewDidFinishLoad()之前,故JS无法调用OC方法。

解决方法:

1)可在JS中构建一个事件 “webViewDidFinishLoad()”,在原生的webVIewDidFinishLoad()方法中通过context的evaluateScript方法或者webView的stringByEvaluatingJavaScriptFromString()方法来调用。

[self.context evaluateScript:@"webViewDidFinishLoad()"];

[webView stringByEvaluatingJavaScriptFromString:@"webViewDidFinishLoad()"];

2)通过JS延时操作setTimeout使得该方法在OC加载完毕后再执行

setTimeout("showMessage('参数在此')",1000)

三)交互方式

1.Block

1)JS调用OC

self.context[@"showMessage"] = ^(NSString *message){

NSLog(@"----showMessage-JS调用OC----参数为:%@",message); };

<button onclick="showMessage('我是参数')">showMessage-JS调用OC</button>

2)OC调用JS

//1.调用JS语句

[self.context evaluateScript:@"alert('你好!');"];

//2.调用JS中的变量

JSValue *myValue = self.context[@"myObject"];

NSLog(@"---%@---",[myValue toString]);

//3.调用JS中的方法(以下两种均可)

[self.context evaluateScript:@"OcCallJs()"];

[webView stringByEvaluatingJavaScriptFromString:@"OcCallJs()"];

//调用有参数的方法

JSValue *news = self.context[@"showNews"];

[news callWithArguments:@[@"新闻头条",@"时事政治"]];

<script>

var myObject = "我的项目哈哈哈";

function OcCallJs(){

alert("我被OC调用了");

}

function showNews(news,news2){
                   alert(news+news2);
            }

</script>

2.Delegate交互

当OC方法为多参数函数时,其在JS中调用需要更改一下以适应JS语法:

1)移除所有冒号

2)跟在冒号后面的参数的第一个字母大写

@protocol JSInteraction

-(void)showDelegateMessage:(NSString *)message;

-(void)showDelegateMessage:(NSString *)message andNumber:(NSInteger)number;

@end

-(void)showDelegateMessage:(NSString *)message{

NSLog(@"我是被JS调用的OC的delegate方法,参数为%@",message);

}

-(void)showDelegateMessage:(NSString *)message andNumber:(NSInteger)number{

NSLog(@"delegate多参数,参数为%ld--%@",number,message);

}

<script>

function webViewDidFinish(){

JsInteractive.showDelegateMessage('参数');

JsInteractive.showDelegateMessageAndNumber('123','1000000');

}

</script>

OcCallJs()方法在上文提到的webVIewDidFinishLoad()中调用

三、WKWebView

(一)定义

1.WKWebView

两种创建方式

//普通初始化

self.wkWebView = [[WKWebView alloc]init];

self.wkWebView.frame = self.view.bounds;

//config方式初始化

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];

self.wkWebView = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:config];

self.wkWebView.navigationDelegate = self;

self.wkWebView.UIDelegate = self;

[self.view addSubview:self.wkWebView];

加载网页的方式

//加载本地URL文件

- (nullableWKNavigation*)loadFileURL:(NSURL*)URL allowingReadAccessToURL:(NSURL*)readAccessURL

//加载本地HTML字符串

- (nullableWKNavigation*)loadHTMLString:(NSString*)string baseURL:(nullableNSURL*)baseURL;

//加载二进制数据

- (nullableWKNavigation*)loadData:(NSData*)data MIMEType:(NSString*)MIMEType characterEncodingName:(NSString*)characterEncodingName baseURL:(NSURL*)baseURL

2.WKWebViewConfiguration

如上代码,webView初始化时配置webView的属性,JS调用原生时需要用到。

3.WKUserContentController

这个类主要用来负责原生与JS的交互管理

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

- (void)removeScriptMessageHandlerForName:(NSString *)name;

addScriptMessageHandler:name:方法注册要被JS调用的方法的名称,然后在对应的JavaScript中使用

window.webkit.messageHandlers.<name>.postMessage(<data>)

方法来向native发送消息。最后记得将方法名remove掉,否则会造成内存泄漏。

4.WKScriptMessageHandler

用来处理JS调用原生的方法

//接受JS发给原生的消息

- (void)userContentController:(WKUserContentController *)userContentControllerdidReceiveScriptMessage:(WKScriptMessage *)message;

5.WKUIDelegate

协议主要用于WKWebView处理web界面的三种提示框(警告框、确认框、输入框)。提供用原生控件显示网页的方法回调。

(二)建立连接

1.文件头部引入相关库

#import <WebKit/WebKit.h>

2.加载HTML页面

NSURL *urlString = [[NSBundle mainBundle] URLForResource:@"JSInteraction.html" withExtension:nil];

if (urlString) {

[_wkWebView loadRequest:[NSURLRequest requestWithURL:urlString]];

}

WKUserContentController *userCC = config.userContentController;

(三)交互方式

1.原生调用JS

WKWebView 提供了一个新的方法evaluateJavaScript:completionHandler:,实现OC 调用JS 等场景。

//获取到input标签的value属性值

NSString *inputValue = @"document.getElementsByName('input')[0].attributes['value'].value";
    
    /** native 调用JS的方法 */
    [webView evaluateJavaScript:inputValue completionHandler:^(id _Nullable response, NSError * _Nullable error) {
        NSLog(@"value:%@,error:%@",response,error);
    }];

2.JS调用原生

/** JS调用OC  **/
    //此处相当于监听了JS中showMobile这个方法
    [self.userCC addScriptMessageHandler:self name:@"showMobile"];
    [self.userCC addScriptMessageHandler:self name:@"showName"];

//移除WKUserContentController,防止内存泄漏
-(void)removeAllScriptHandler{
    [self.userCC removeScriptMessageHandlerForName:@"showMobile"];
    [self.userCC removeScriptMessageHandlerForName:@"showName"];
    [self.userCC removeScriptMessageHandlerForName:@"showTwo"];
}

JS中:

//多参数用数组传递
        function btnClick1() {            window.webkit.messageHandlers.showMobile.postMessage(null)        }
        function btnClick2() {
            window.webkit.messageHandlers.showName.postMessage('有个参数')
        }
        function  btnClick3() {
            window.webkit.messageHandlers.showTwo.postMessage(['有两个参数啦','canshu'])
        }

三、拦截URL

(一)定义

我们要拦截URL,就要通过navigationDelegate的一个代理方法来实现。如果在HTML中要使用alert等弹窗,就必须得实现UIDelegate的相应代理方法。

(二)建立连接

同WKWebView。

(三)交互方式

1.JS调用OC

2.OC 调用 JS

JS 调用OC 方法后,有的操作可能需要将结果返回给JS。这时候就是OC 调用JS 方法的场景。

WKWebView 提供了一个新的方法evaluateJavaScript:completionHandler:,实现OC 调用JS 等场景。

使用自定义loadURL的原因:

如果当前网页正使用window.location.href加载网页的同时,调用window.location.href去调用OC原生方法,会导致加载网页的操作被取消掉。同样的,如果连续使用window.location.href执行两次OC原生调用,也有可能导致第一次的操作被取消掉。

自定义asyncAlert的原因

因为有的JS调用是需要OC 返回结果到JS的。stringByEvaluatingJavaScriptFromString是一个同步方法,会等待js 方法执行完成,而弹出的alert 也会阻塞界面等待用户响应,所以他们可能会造成死锁。导致alert 卡死界面。如果回调的JS 是一个耗时的操作,那么建议将耗时的操作也放入setTimeout的function中。

3.同步和异步

因为 iOS SDK 没有天生支持 js 和 native

相互调用,大家的技术方案都是自己实现的一套调用机制,所以这里面有同步异步的问题。细心的同学就能发现,js 调用 native 是通过插入一个

iframe,这个 iframe 插入完了就完了,执行的结果需要 native 另外用

stringByEvaluatingJavaScriptFromString 方法通知 js,所以这是一个异步的调用。

而 stringByEvaluatingJavaScriptFromString 方法本身会直接返回一个 NSString 类型的执行结果,所以这显然是一个同步调用。

所以 js call native 是异步,native call js 是同步。

四、WebViewJavascriptBridge

1.实质:拦截URL来实现的调用原生功能

2.JS中实现的方法

function setupWebViewJavascriptBridge(callback) {
                if (window.WebViewJavascriptBridge) {
                    return callback(WebViewJavascriptBridge);
                }
                if (window.WVJBCallbacks) {
                    return window.WVJBCallbacks.push(callback);
                }
                window.WVJBCallbacks = [callback];
                var WVJBIframe = document.createElement('iframe');
                WVJBIframe.style.display = 'none';
                WVJBIframe.src = 'https://__bridge_loaded__';
                document.documentElement.appendChild(WVJBIframe);
                setTimeout(function() {
                    document.documentElement.removeChild(WVJBIframe)
                     }, 0)
            }

这个方法的作用主要是在第一次加载HTML的时候起作用,目的是加载一次wvjbscheme://__BRIDGE_LOADED__,来触发往HTML中注入一些已经写好的JS方法。

setupWebViewJavascriptBridge(function(bridge) {
               /* Initialize your app here */
               bridge.registerHandler('JS Echo', function(data, responseCallback) {
                                                            console.log("JS Echo called with:", data)
                                                            responseCallback(data)
                                                            })
                bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
                            console.log("JS received response:", responseData)
                                                        })
                    })

Native 需要调用的 JS 功能,也是需要先注册,然后再执行的。如果Native 需要调用的JS 功能有多个,那么这些功能都要在这里先注册,注册之后才能够被Native 调用。

参照:

https://www.jianshu.com/p/7151987f012d

http://blog.devtang.com/2012/03/24/talk-about-uiwebview-and-phonegap/

OC与JS交互的更多相关文章

  1. OC和JS交互的三种方法

    看简书上说一共有六种OC和JS交互的方法,但是前三种原理都一致,都是通过检测.拦截Url地址实现互相调用的.剩下的react native等第三方框架原理不一样,也没有去研究,下边记录我使用的三种方法 ...

  2. Mac Webview OC与JS交互实现

    1.首先,需要定义一个JS可识别的变量(如external)用于OC与JS交互 - (void)webView:(WebView *)sender didClearWindowObject:(WebS ...

  3. iOS(UIWebView 和WKWebView)OC与JS交互 之二

    在iOS应用的开发过程中,我们经常会使用到WebView,当我们对WebView进行操作的时候,有时会需要进行源生的操作.那么我记下来就与大家分享一下OC与JS交互. 首先先说第一种方法,并没有牵扯O ...

  4. OC与JS交互之WebViewJavascriptBridge

    上一篇文章介绍了通过UIWebView实现了OC与JS交互的可能性及实现的原理,并且简单的实现了一个小的示例DEMO,当然也有一部分遗留问题,使用原生实现过程比较繁琐,代码难以维护.这篇文章主要介绍下 ...

  5. OC与JS交互之UIWebView

    随着H5的强大,hybrid app已经成为当前互联网的大方向,单纯的native app和web app在某些方面显得就很劣势.关于H5的发展史,这里有一篇文章推荐给大家,今天我们来学习最基础的基于 ...

  6. OC与JS交互前言

    OC与JS交互过程中,可能会需要使用本地image资源及html,css,js文件,这些资源应该如何被加载? 一.WebView加载HTML UIWebView提供了三个方法来加载html资源 1.  ...

  7. WebViewJavascriptBridge源码探究--看OC和JS交互过程

    今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了stringByEvaluatingJavaScriptFr ...

  8. iOS(WKWebView)OC与JS交互 之三

      随着H5功能愈发的强大,没进行过混合开发的小伙们都不好意思说自己能够独立进行iOS的app开发,在iOS7操作系统下,常用的native,js交互框架有easy-js,WebViewJavascr ...

  9. OC与JS交互之WKWebView

    上一篇文章我们使用了JavaScriptCore框架重写了之前的示例,iOS8苹果偏爱HTML5,重构了UIWebVIew,给我们带来了WKWebView,使其性能.稳定性.功能大幅度提升,也更好的支 ...

  10. OC与JS交互之JavaScriptCore

    JavaScriptCore提供了JavaScript和Objective-C桥接的Obj-C API.JavaScriptCore提供了让我们脱离UIWebView执行JavaScript脚本的能力 ...

随机推荐

  1. 虚拟机linux系统网络连接配置问题总结

    1.虚拟机与CentOS的安装与配置参考本人博客:https://www.cnblogs.com/ClikeL/p/11743520.html 2.测试网络连接 ping www.baidu.com ...

  2. 关于button在td中时,zclip复制不能的问题

    是button的定位问题引起的,解决方案:套上div加上position:relative即可 <td> <div style="position:relative&quo ...

  3. YAML(YML)语法详解

    ansible playbook是由yaml(yml)语法书写,结构清晰,可读性强,所以必须掌握yaml(yml)基础语法 语法 描述 锁进  YAML使用固定的缩进风格表示层级结构,每个缩进由两个空 ...

  4. vue项目接入百度地图

    方法一 :使用第三方工具 vue-baidu-map 安装命令: yarn add vue-baidu-map --save 文档地址:https://dafrok.github.io/vue-bai ...

  5. python 中模块的版本号

    查看所使用的模块的版本号,以numpy为例 import numpy numpy.__version__ 查看help(numpy)时,信息太多,不想看了,如何退出,按q,即可.

  6. 后端分页神器,mybatis pagehelper 在SSM与springboot项目中的使用

    mybatis pagehelper想必大家都耳熟能详了,是java后端用于做分页查询时一款非常好用的分页插件,同时也被人们称为mybatis三剑客之一,下面 就给大家讲讲如何在SSM项目和sprin ...

  7. P1216 [IOI1994]数字三角形

    史上最水的 dp 题,没有之一(By rxz) 确实很简单,就算是我这个 dp 萌新也一眼看出来了转移方程 首先考虑状态,设 \(f_{i,j}\) 表示选择第 \(i\) 层第 \(j\) 个数时获 ...

  8. Bugku-CTF之web8(txt????)

    Day29  

  9. Codeforces Round #599 (Div. 2) C. Tile Painting

    Ujan has been lazy lately, but now has decided to bring his yard to good shape. First, he decided to ...

  10. AcWing 841. 字符串哈希

    //快速判断两次字符串是不是相等 #include<bits/stdc++.h> using namespace std ; typedef unsigned long long ULL; ...