OC与JS交互

 一、简述

目前原生与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 调用。

参照:

//www.greatytc.com/p/7151987f012d

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容