4887王中王鉄算盘奖结果看OC和JS交互过程,源码解

来源:http://www.smjxgs.com 作者:操作系统 人气:170 发布时间:2019-08-09
摘要:WebViewJavascriptBridge源码探究--看OC和JS交互过程,wkwebviewjs交互oc       今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了s

WebViewJavascriptBridge源码探究--看OC和JS交互过程,wkwebviewjs交互oc

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

。现在主要是了解js是如何调用oc方法的,分享下探究过程。

   源码不多,就一个头文件WebViewJavascriptBridge.h和实现文件WebViewJavascriptBridge.m, 和一个js文件,实现在js那边可以调用oc方法,也可以在oc里面调用js方法。

先上图,实现简单的oc和js互相调用的demo, 另外附加一个模拟项目中用到的oc和js互相调用场景:

  4887王中王鉄算盘奖结果 1

 

一、然后说说js调用oc方法的原理,它们是如何实现的?库文件三个

4887王中王鉄算盘奖结果 2

 

我们跟踪下oc控制器加载UIWebView的过程和js调用oc方法过程

 

1、程序启动,在自定义控制器里,创建一个WebViewJavascriptBridge对象时,会加载WebViewJavascriptBridge.js.txt文件,里面是初始js代码

     在这个js里面,创建了一个WebViewJavascriptBridge脚本对象,另外创建一个隐藏的iframe标签:每次js调用oc方法,都是修改iframe标签的src来触发UIWebView的代理监听方法

4887王中王鉄算盘奖结果 3;(function() { if (window.WebViewJavascriptBridge) { return } var messagingIframe var sendMessageQueue = [] var receiveMessageQueue = [] var messageHandlers = {} var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme' var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__' var responseCallbacks = {} var uniqueId = 1 function _createQueueReadyIframe(doc) { messagingIframe = doc.createElement('iframe') messagingIframe.style.display = 'none' messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE doc.documentElement.appendChild(messagingIframe) } function init(messageHandler) { if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice') } WebViewJavascriptBridge._messageHandler = messageHandler var receivedMessages = receiveMessageQueue receiveMessageQueue = null for (var i=0; i<receivedMessages.length; i ) { _dispatchMessageFromObjC(receivedMessages[i]) } } function send(data, responseCallback) { _doSend({ data:data }, responseCallback) } function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler } function callHandler(handlerName, data, responseCallback) { _doSend({ handlerName:handlerName, data:data }, responseCallback) } function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' (uniqueId ) '_' new Date().getTime() responseCallbacks[callbackId] = responseCallback message['callbackId'] = callbackId } sendMessageQueue.push(message); //将字典放入数组 //修改iframe标签的src属性,UIWebView监听执行代理方法 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE; } function _fetchQueue() { //json数组转成json字符串 var messageQueueString = JSON.stringify(sendMessageQueue) sendMessageQueue = [] return messageQueueString } function _dispatchMessageFromObjC(messageJSON) { setTimeout(function _timeoutDispatchMessageFromObjC() { var message = JSON.parse(messageJSON) var messageHandler if (message.responseId) { var responseCallback = responseCallbacks[message.responseId] if (!responseCallback) { return; } responseCallback(message.responseData) delete responseCallbacks[message.responseId] } else { var responseCallback if (message.callbackId) { var callbackResponseId = message.callbackId responseCallback = function(responseData) { _doSend({ responseId:callbackResponseId, responseData:responseData }) } } var handler = WebViewJavascriptBridge._messageHandler if (message.handlerName) { handler = messageHandlers[message.handlerName] } try { handler(message.data, responseCallback) } catch(exception) { if (typeof console != 'undefined') { console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception) } } } }) } function _handleMessageFromObjC(messageJSON) { if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON) } else { _dispatchMessageFromObjC(messageJSON) } } window.WebViewJavascriptBridge = { init: init, send: send, registerHandler: registerHandler, callHandler: callHandler, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC } var doc = document _createQueueReadyIframe(doc) var readyEvent = doc.createEvent('Events') readyEvent.initEvent('WebViewJavascriptBridgeReady') readyEvent.bridge = WebViewJavascriptBridge doc.dispatchEvent(readyEvent) })(); View Code

4887王中王鉄算盘奖结果 4

4887王中王鉄算盘奖结果 54887王中王鉄算盘奖结果 6

 

2、UIWebView加载我们自定义的html页面TestJSBridge.html, 里面有脚本注册js调用oc方法标识,和oc调用js标识

4887王中王鉄算盘奖结果 7<html> <head> <meta charset="utf-8"/> <style type="text/css"> html { font-family:Helvetica; color:#222; background:#D5FFFD; border: 5px dashed blue;} .rowH3{margin: 0px; text-align: center;} .jsBtn{font-size: 18px;} </style> </head> <body> <h3 class="rowH3">测试OC和JS互相调用</h3> <button class="jsBtn" id="jsBtn">JS调用OC方法</button> <div id="logDiv"><!-- 打印日志 --></div> </body> </html> <script type="text/javascript"> window.onerror = function(err) { printLog(err); } function connectWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { callback(WebViewJavascriptBridge); } else { document.addEventListener('WebViewJavascriptBridgeReady', function() { callback(WebViewJavascriptBridge); }, false); } } var uniqueId = 1; //日志打印方法 function printLog(data) { var logObj = document.getElementById('logDiv'); var el = document.createElement('div'); el.className = 'logLine'; el.innerHTML = uniqueId ': ' JSON.stringify(data); //json转字符串 if (logObj.children.length) { logObj.insertBefore(el, logObj.children[0]) } else { logObj.appendChild(el) } } //初始化调用函数connectWebViewJavascriptBridge connectWebViewJavascriptBridge(function(bridge) { bridge.init(function(message, responseCallback) {}); //注册js响应方法,响应OC调用,标识objc_Call_JS_Func bridge.registerHandler('objc_Call_JS_Func', function(data, responseCallback) { printLog(data); //打印oc传过来的参数 }); //给标签按钮设置点击事件 var callbackButton = document.getElementById('jsBtn'); callbackButton.onclick = function(e) { e.preventDefault(); //注册标识js_Call_Objc_Func,便于js给IOS发送消息 bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我从js那边过来!'}, function(response) { }); } }); </script> View Code

 

3、点击html标签按钮,触发js事件

//给标签按钮设置点击事件
  var callbackButton = document.getElementById('jsBtn');
  callbackButton.onclick = function(e) {
   e.preventDefault();
            //注册标识js_Call_Objc_Func,便于js给IOS发送消息
   bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我从js那边过来!'},  function(response) { });
  }

 我们跟踪bridge.callHandler方法,进入WebViewJavascriptBridge.js

  var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'

  var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

4887王中王鉄算盘奖结果 8

    messagingIframe是个iframe标签,点击我们自定义html按钮标签,触发js事件,最后进入callHandler -->  _doSend ,

当messagingIframe标签src重新赋值时,会触发UIWebView的代理方法(src的值一直是:wvjbscheme://__WVJB_QUEUE_MESSAGE__ ,也可自定义,这个在进入oc UIWebView代理方法时会用来作为判断标识)。

跟踪后面执行的过程:

  4887王中王鉄算盘奖结果 9

4887王中王鉄算盘奖结果 10

4887王中王鉄算盘奖结果 11

4887王中王鉄算盘奖结果 12

至此,js调用oc成功

总结js调用oc过程:

-->   触发js事件

-->   把要传入参数和自定义注册标识“js_Call_Objc_Func”存入js数组sendMessageQueue

 -->  重新赋值iframe标签的src属性,触发UIWebView代理方法, 根据src的值进入相应处理方法中

-->   在oc方法里面调用js方法_fetchQueue, 获取js数组里面所有的参数  

-->   根据传入的自定义注册标识 js_Call_Objc_Func  从oc字典_messageHandlers找出匹配block, 最后执行block,里面有我们自定义处理的后续代码

 

二、oc调用js过程

从oc内部发起

-- > 调用bridge的callHandler方法,传入需要的参数和自定义注册标识

--> 最后使用UIWebView系统方法stringByEvaluatingJavaScriptFromString调用js脚本WebViewJavascriptBridge._handleMessageFromObjC 完成参数的传递

  

WebViewJavaScriptBridge是IOS中JS和OC交互的常用框架,支持以下两种调用:

一、用法简析

---------------------------------------------  end

DEMO下载

github地址:

 

另外记录一个UIWebView不能加载带中文参数的url问题:

假设加载url为:

这样UIWebView加载这个带中文参数的url, 是不能显示的,需要把中文进行转义,才能显示。

使用字符串方法stringByAddingPercentEncodingWithAllowedCharacters对中文进行转义

NSString *str = @"http://baidu.com/?search=博客园";
  //  str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}"[]|\<> "].invertedSet];

 NSURL *url = [NSURL URLWithString:str];

  NSURLRequest *request = [NSURLRequest requestWithURL:url];

 

原文链接:

 

今天把实现OC代码和JS代码交互的第三方库 WebViewJavascriptBridge源码看了下,o...

1. OC端的方法如下

4887王中王鉄算盘奖结果 13

4887王中王鉄算盘奖结果 14

这个库的还是比较精简单的,当前webView 是用UIWebView 那么我只需要引入WebViewJavascriptBridge ,相应的WKWebView则需要用到WKWebViewJavascriptBridge

Method Frome OC

这个桥接主要是介于js 和 app 间我们先来看js 要怎么做,我们需要copy 这段代码到js里至于为什么要这么做,后文会提到

Method 1 是注册一个OC的方法--testObjcCallback,handler是JS掉用的内容,responseCallback是将OC处理返回给JS的回调(对应的是上述第2种调用);

function setupWebViewJavascriptBridge(callback) {

Method 2 是调用JS的方法的testJavascriptHandler方法,@{ @"foo":@"before ready" }是需要传递的参数,responseCallback是将JS处理结果返回给OC的回调(对应的是上述的第1种调用)

if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }

2. JS端的方法如下

if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }

4887王中王鉄算盘奖结果 15

window.WVJBCallbacks = [callback];

Method Frome JS

var WVJBIframe = document.createElement('iframe');

Method 1 是JS注册一个方法供OC调用,responseCallback(responseData)是将处理结果返回OC。

WVJBIframe.style.display = 'none';

Method 2 是在点击了一个按钮之后JS调用OC的方法,{'foo': 'bar'}是给OC的参数,response是OC处理后返回给JS的数据。

WVJBIframe.src = '';

注:JS中是可以不写;号的,这和swift一样

document.documentElement.appendChild(WVJBIframe);

JS调用OC,OC将处理结果回调给JS:要想被JS调用,我们首先要注册一个handler,和回调的            block,注册时候以键值对的形式存储这个block,handler,当JS调用OC时调用webView:shouldStartLoadWithRequest:navigationType:这个方法,根据JS传来的数据,找到之前保存的Block并且调用,同时新建一个需要把处理结果回调给JS的Blcok,OC处理完结果之后调用刚才创建的Block利用stringByEvaluatingJavaScriptFromString将处理结果返回给JS。

setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)

OC调用JS时与此类似。基于这个流程,我们来看WebViewJavaScriptBridge的实现过程。

}

原理

export default class IOSBridge {

接下来我们来分析从页面加载到OC和JS互相调用的整个过程:

// key : xxx  参数 : json  回调: responseCallback

一、准备工作

changeState (json) {

当加载HTML文件的时候调用[webView loadHTMLString:appHtml baseURL:baseURL];,这时会调用:

setupWebViewJavascriptBridge(function (bridge) {

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {if(webView != _webView) {returnYES; }NSURL*url = [request URL];    __strongWVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;if([_base isWebViewJavascriptBridgeURL:url]) {if([_base isBridgeLoadedURL:url]) {            [_base injectJavascriptFile];        }elseif([_base isQueueMessageURL:url]) {NSString*messageQueueString = [self_evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];            [_base flushMessageQueue:messageQueueString];        }else{            [_base logUnkownMessage:url];        }returnNO;    }elseif(strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {return[strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];    }else{returnYES;    }}

bridge.callHandler('xxx', json, function responseCallback (responseData) {


})

在这个方法中判断URL的类型,如果是WebViewJavascriptBridgeURL那么就会判断是BridgeLoadedURL,QueueMessageURL还是未知的URL,在首次调用时是返回YES的,然后的URL就是BridgeLoadedURL,我们在看它的判断条件[self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded];Scheme是自己设置的https,那么BridgeLoaded(__bridge_loaded__)是什么呢?我们看ExampleApp.html文件,发现它的script标签中有这么一段代码:

})

functionsetupWebViewJavascriptBridge(callback){if(window.WebViewJavascriptBridge) {returncallback(WebViewJavascriptBridge); }if(window.WVJBCallbacks) {returnwindow.WVJBCallbacks.push(callback); }window.WVJBCallbacks = [callback];varWVJBIframe =document.createElement('iframe');    WVJBIframe.style.display ='none';    WVJBIframe.src =');    setTimeout(function(){document.documentElement.removeChild(WVJBIframe) },0)}setupWebViewJavascriptBridge(function(bridge){varuniqueId =1functionlog(message, data){varlog =document.getElementById('log')varel =document.createElement('div')        el.className ='logLine'el.innerHTML = uniqueId '. ' message ':
' JSON.stringify(data)if(log.children.length) { log.insertBefore(el, log.children[0]) }else{ log.appendChild(el) }    }

}


}

在这里我们发现了

app 端只需要

[_base injectJavascriptFile]

_bridge = [WebViewJavascriptBridge bridgeForWebView:webView];

注入一个JS文件,这个JS文件的主要内容是(篇幅问题,有删减):

[_bridge setWebViewDelegate:self];

window.WebViewJavascriptBridge = {registerHandler: registerHandler,callHandler: callHandler,disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,_fetchQueue: _fetchQueue,_handleMessageFromObjC: _handleMessageFromObjC};varmessagingIframe;varsendMessageQueue = [];varmessageHandlers = {};varCUSTOM_PROTOCOL_SCHEME ='https';varQUEUE_HAS_MESSAGE ='__wvjb_queue_message__';varresponseCallbacks = {};varuniqueId =1;vardispatchMessagesWithTimeoutSafety =true;functionregisterHandler(handlerName, handler){    messageHandlers[handlerName] = handler;}functioncallHandler(handlerName, data, responseCallback){    _doSend();}functiondisableJavscriptAlertBoxSafetyTimeout(){    dispatchMessagesWithTimeoutSafety =false;}function_doSend(message, responseCallback){    sendMessageQueue.push(message);    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE;}function_fetchQueue(){varmessageQueueString =JSON.stringify(sendMessageQueue);    sendMessageQueue = [];returnmessageQueueString;}function_dispatchMessageFromObjC(messageJSON){if(dispatchMessagesWithTimeoutSafety) {        setTimeout(_doDispatchMessageFromObjC);    }else{        _doDispatchMessageFromObjC();    }function_doDispatchMessageFromObjC(){        }    }}function_handleMessageFromObjC(messageJSON){    _dispatchMessageFromObjC(messageJSON);}messagingIframe =document.createElement('iframe');messagingIframe.style.display ='none';messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE;document.documentElement.appendChild(messagingIframe);registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);setTimeout(_callWVJBCallbacks,0);function_callWVJBCallbacks(){varcallbacks =window.WVJBCallbacks;deletewindow.WVJBCallbacks;for(vari=0; i

[self.bridge registerHandler:@"xxx" handler:^(id data, WVJBResponseCallback responseCallback) {


responseCallback(@"xxx");

下面我们来分析下注入的JavaScript的内容。

}];

给window对象添加一个属性WebViewJavascriptBridge(JS中可以直接给对象添加属性),这个对象包含以下内容:

简洁的代码就已经完成js与app的桥接,那么我们来看看他整个流程是怎么执行,

  1) registerHandler:注册调用方法

首先js 调用的代码出现一个了创建了一个iframe 并且设置src = ''

  2)callHandler:调用OC时的方法

我们很清楚的知道如果WKWebView 可以捕获到url 的变化的 也就是说设置src后WKWebView 是有回调的,那么我把断点断到WKWebView的代理看看这里发生了什么(PS:为什么这里iframe 要appendChild到document, 然后又快速的remove 呢 ,我想到一个解释 ,是因为iframe 设置src 就无用了,iframe 只能通过 removeChild 才能移除掉,然后要想remove 就必须得先append )

  3)disableJavscriptAlertBoxSafetyTimeout:超时时弹框是否展示的标示

原理简释:

  4)_fetchQueue:获取Queue对象的方法

当js 第一次app 桥接 我们在代理截获到 这段

  5)_handleMessageFromObjC:处理OC调用的方法

4887王中王鉄算盘奖结果 16

2.定义了一系列的变量来存储数据

我们截获到 这段url

messagingIframe:iframe标签,当我们的WebView加载它的时候,会调用其中的src,src就是调用请求的URL。

这里有个loaded 我可以猜测类似ViewDidLoad 页面加载完成,那么我们进代理看看做了什么

1)sendMessageQueue:message数组  2)messageHandlers:handler对象 *JS中{}表示对象*  3)CUSTOM_PROTOCOL_SCHEME:scheme标示  4)QUEUE_HAS_MESSAGE:有Message标识  5)responseCallbacks:回调对象  6)uniqueId:唯一标示ID

if ([_base isWebViewJavascriptBridgeURL:url]) {

进过系列一的剖析,我们明白了使用WebViewJavaScriptBridge前需要做的准备工作,那么接下来,我们一起探讨OC和JS相互调用的具体执行过程以及其中的要点。

if ([_base isBridgeLoadedURL:url]) {

二、 JS调用OC,然后OC将处理结果返回JS

1. OC首先注册JS将调用的方法

OC调用registerHandler:,这时将其调用信息存储在messageHandlers字典中以handlerName为Key,给JS处理结果的Block为Value(_base.messageHandlers[handlerName] = [handler copy]);

2. 在JS中调用被注册的方法

JS调用

bridge.callHandler('testObjcCallback', {'foo':'bar'},function(response){            log('JS got response', response)        })

来调用上文OC注册的方法,这个brige就是上文注入JS代码时候创建的,我们再它内部做了什么。

functioncallHandler(handlerName, data, responseCallback){if(arguments.length ==2&&typeofdata =='function') {        responseCallback = data;        data =null;    }    _doSend({handlerName:handlerName,data:data }, responseCallback);}

这里判断了参数类型,如果传入的参数只有两个,并且第二个是function类型,那么就将第二个参数变为callBack,data置空,将handlerName和data转化成一个对象的两个属性并传给_doSend()。

function_doSend(message, responseCallback){if(responseCallback) {varcallbackId ='cb_' (uniqueId ) '_' newDate().getTime();                responseCallbacks[callbackId] = responseCallback;                message['callbackId'] = callbackId;            }            sendMessageQueue.push(message);            messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE;        }


这里的responseCallback是JS先调用OC然后OC调用JS时才会有的,如果这种情况,那么需要用唯一的标识(callbackId),来将这个responseCallback存储在responseCallbacks中,并且给message添加callbackId这个属性。这个数值会在下次OC调用JS的时候作为唯一的Key被用到。软后将message放入:sendMessageQueue队列中,然后拼接src。

3. 在回掉方法中拦截相应的方法,然后调用block.

经过方法步骤2,会调用下面的回调方法

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType{}

在这个方法中调用

NSString*messageQueueString = [self_evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];  [_base flushMessageQueue:messageQueueString];

首先获取JS中的messageQueue(步骤2中的sendMessageQueue),然后调用flushMessageQueue:方法:

idmessages = [self_deserializeMessageJSON:messageQueueString];for(WVJBMessage* messageinmessages) {if(![message isKindOfClass:[WVJBMessageclass]]) {NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messageclass], message);continue;    }    [self_log:@"RCVD"json:message];/////////*********OC先调用了JS,JS再调用了OC*********///////////NSString* responseId = message[@"responseId"];if(responseId) {//调用之前存储的BolckWVJBResponseCallback responseCallback = _responseCallbacks[responseId];        responseCallback(message[@"responseData"]);        [self.responseCallbacks removeObjectForKey:responseId];/////////*********JS先调用OC,OC再调用JS*********////////////// 这里是JS先调用OC的时候存储的是 JS的回调函数}else{// JS先调用的OC,OC再调用JSWVJBResponseCallback responseCallback =NULL;NSString* callbackId = message[@"callbackId"];if(callbackId) {            responseCallback = ^(idresponseData) {if(responseData ==nil) {                    responseData = [NSNullnull];                }//JS调用OC时候的存储(后续OC调用JS返回计算结果)WVJBMessage* msg = @{@"responseId":callbackId,@"responseData":responseData };                [self_queueMessage:msg];            };        }else{            responseCallback = ^(idignoreResponseData) {// Do nothing};        }                WVJBHandler handler =self.messageHandlers[message[@"handlerName"]];if(!handler) {NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);continue;        }//调用OC的Block,同时,如果OC调用responseCallback,则调用_queueMessage进行相应的处理handler(message[@"data"], responseCallback);    }}


这里先将返回的JSON字符串转换成对象,这里的字符串是调用

function_fetchQueue(){varmessageQueueString =JSON.stringify(sendMessageQueue);    sendMessageQueue = [];returnmessageQueueString;  }

获取的,这里将sendMessageQueue转为JSON,然后将其置空,这里为啥使用数组而不用对象来存储呢?因为可能JS还没有处理结束就有两次调用,要保证他们不丢失使用了数组。然后判断数组中的Message对象是否有responseId(JS调用OC第一次时存储的),这里没有responseId所以走else:如果有callbackId(在JS中作为回调用的),定义responseCallback,这个block就是OC将处理结果返回给JS时用到的block。如果没有callbackId说明,不需要回调JS,这个时候responseCallback为空。最后调用步骤1中存储在messageHandlers对象中的block,并且将刚才创建的responseCallback作为参数传入,以便OC将计算结果传递给JS。

4. OC将计算结果返回给JS

[_bridge registerHandler:@"testObjcCallback"handler:^(iddata, WVJBResponseCallback responseCallback) {NSLog(@"testObjcCallback called: %@", data);    responseCallback(@"response form oc's call back");  }];

在handler的最后一步调用responseCallback()将处理结果回调给JS。这个responseCallback()就是我们在步骤3中创建的responseCallback。我们再来看这个block。看步骤3可以看到这个其内部调用

[self_queueMessage:msg];  [self_dispatchMessage:message];

在_dispatchMessage内部执行:

NSString* javascriptCommand = [NSStringstringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];

接下来JS中的_handleMessageFromObjC就会接收到OC传过来处理结果。

function_4887王中王鉄算盘奖结果,doDispatchMessageFromObjC(){varmessage =JSON.parse(messageJSON);varmessageHandler;varresponseCallback;if(message.responseId) {            responseCallback = responseCallbacks[message.responseId];if(!responseCallback) {return;            }            responseCallback(message.responseData);deleteresponseCallbacks[message.responseId];        }else{// OC先调用JS是用到}    }

这个时候我们看到了步骤三中的responseId的作用了,这时候responseId就表明了是OC将处理结果传递给JS并不需要JS再调用OC了,这时只调用responseCallback(message.responseData);将数据传给JS。

这样我们就完成了JS调用OC,然后OC将结果回调给JS的全部过程。

[_base injectJavascriptFile];

三、OC调用JS,然后JS将处理结果返回给OC

1. JS注册相应的方法供回调

同OC注册方法时候一样,JS也是用一个messageHandlers对象来存储

functionregisterHandler(handlerName, handler){    messageHandlers[handlerName] = handler;    }

2. OC调用JS时存储调用信息

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {NSMutableDictionary* message = [NSMutableDictionarydictionary];if(data) {            message[@"data"] = data;        }if(responseCallback) {NSString* callbackId = [NSStringstringWithFormat:@"objc_cb_%ld", _uniqueId];self.responseCallbacks[callbackId] = [responseCallbackcopy];            message[@"callbackId"] = callbackId;        }if(handlerName) {            message[@"handlerName"] = handlerName;        }        [self_queueMessage:message];    }

这里使用message字典来存储参数,方法名,使用responseCallbacks来存储JS处理完之后,需要回调的Block(这里为了确保多次调用不会覆盖之前的调用,使用了唯一的callbackId)。

同上文所述,最终会调用

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand {return[_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];    }

3. JS调用_dispatchMessageFromObjC

这时message没有responseId,会走else,

if(message.callbackId) {varcallbackResponseId = message.callbackId;                    responseCallback =function(responseData){                        _doSend({handlerName:message.handlerName,responseId:callbackResponseId,responseData:responseData });                    };                }varhandler = messageHandlers[message.handlerName];if(!handler) {console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);                }else{                    handler(message.data, responseCallback);                } 这里定义了需要给OC传递结果的`responseCallback`,取出之前注册的`handler`:`messageHandlers[message.handlerName]`,然后调用这个`handler`,并将这个`responseCallback`作为参数传进去,`handler(message.data, responseCallback);`

4. JS将结果回传给OC

在步骤三中调用handler:function(data, responseCallback){            log('ObjC called testJavascriptHandler with', data)varresponseData = {'Javascript Says':'Right back atcha!'}            log('JS responding with', responseData)            responseCallback(responseData)        } 在这个`handler`的结尾调用步骤三种的`responseCallback`(传入的只有数据没有回调),根据步骤三可以看出来其会调用`_doSend`方法。该方法中由于没有传进去回调,所以不会给message对象添加`callbackId`,只调用        sendMessageQueue.push(message);          messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE;

这是由于含有responseId(在步骤三中的_doSend调用时设置),所以只会取出之前存储的block,并且将结果回传给OC:

//调用之前存储的BolckWVJBResponseCallback responseCallback = _responseCallbacks[responseId];        responseCallback(message[@"responseData"]);        [self.responseCallbacks removeObjectForKey:responseId];

至此,OC和JS交互的所有逻辑已介绍完毕(WKWebView实现方式相同),总结下两种情景的回调,其实现方式及其相似,正如文章开头的总结。

} else if ([_base isQueueMessageURL:url]) {

[self WKFlushMessageQueue];

} else {

[_base logUnkownMessage:url];

}

decisionHandler(WKNavigationActionPolicyCancel);

return;

}

直接点进去我们看到[_base isWebViewJavascriptBridgeURL:url] 这个方法去区分是否是从这个库的iframe 设置的src的url 他们是根据

#define kOldProtocolScheme @"wvjbscheme"

#define kNewProtocolScheme @"https"

#define kQueueHasMessage  @"__wvjb_queue_message__"

#define kBridgeLoaded      @"__bridge_loaded__"

判断url 是否包含以上字符串,那么接着看看当url 是 这个的时候做了什么,也就是执行了这个 [_base injectJavascriptFile] 注入一段js,自然这段js 必然起到重要的作用,我们先简单看一下这段js,也就是这个两个文件

4887王中王鉄算盘奖结果 17

这段js简单来看是插入一个(function(){xxx})()的匿名函数

创建了一个WebViewJavascriptBridge 全局变量

window.WebViewJavascriptBridge = {

registerHandler: registerHandler,

callHandler: callHandler,

disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,

_fetchQueue: _fetchQueue,

_handleMessageFromObjC: _handleMessageFromObjC

};

简单过完这段js 然后我继续调试,发现出现了一个url 为 的变化

4887王中王鉄算盘奖结果 18

那可以猜想是不是告诉app端 js 正在调用你的方法呢,但是很奇怪的是,断点在app方法回调的部分却没有执行,这是为啥,那我们在看看 那段注入的js 做了什么

[self.bridge registerHandler:@"xxx" handler:^(id data, WVJBResponseCallback responseCallback) {

responseCallback(@"xxx");

}];

我们看到注入的js里面有一个iframe 同样也设置了src 代码如下

messagingIframe = document.createElement('iframe');

messagingIframe.style.display = 'none';

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE;

document.documentElement.appendChild(messagingIframe);

那为什么他设置src,这样调试有啥用,是不是为了初始化什么呢,那接着调试看看,他调用了WKFlushMessageQueue这个方法,调用js WebViewJavascriptBridge._fetchQueue()的方法,那我们在来看看注入js 有没有_fetchQueue这个方法,

function _fetchQueue() {

var messageQueueString = JSON.stringify(sendMessageQueue);

sendMessageQueue = [];

return messageQueueString;

}

他是返回了messageQueueString ,并且清空sendMessageQueue 这个数组,可以猜测他估计这一次只是为了重新初始化sendMessageQueue 这个数组,那么这么说下一次就应该是响应js 调用 app的方法了,那么耐着性子继续来下一步,果然WKFlushMessageQueue 注入js 的回调了handelName 和参数

4887王中王鉄算盘奖结果 19

接着在[_base flushMessageQueue:result]; 这个方法将result字符串转换成字典 并且在messageHandlers这个全局变量获取到对应handlerName 的 hanlder 回调给app 注册的回调中

ps: app 端注册hanler 就会把这个hanler 添加messageHandlers这个可变字典中

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {

_base.messageHandlers[handlerName] = [handler copy];

}

说到这里大概知道他是根据代理判断url 变化来截获到js 调用app 这个事件,那到这里还有一个很大疑问就是他的url 他是怎么把参数传过来的了,貌似我们一直在看注入的js ,忽略了刚开始复制那段js 代码,我们在过头来看看

changeState (json) {

setupWebViewJavascriptBridge(function (bridge) {

bridge.callHandler('xxx', json, function responseCallback (responseData) {

})

})

}

这里调用注入的js 的callHandler 方法了要绕回去,下面我会画一个流程图梳理一下整个流程,那我们来看看callHandler方法又做了什么,

function callHandler(handlerName, data, responseCallback) {

if (arguments.length == 2 && typeof data == 'function') {

responseCallback = data;

data = null;

}

_doSend({ handlerName:handlerName, data:data }, responseCallback);

function _doSend(message, responseCallback) {

if (responseCallback) {

var callbackId = 'cb_' (uniqueId ) '_' new Date().getTime();

responseCallbacks[callbackId] = responseCallback;

message['callbackId'] = callbackId;

}

sendMessageQueue.push(message);

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME '://' QUEUE_HAS_MESSAGE;

}

原来在这个时候callHandler 方法message 这个字典塞入sendMessageQueue数组里面,并且设置src 为 通知app 端将发送消息,当代理拦截到这段url 便调用WKFlushMessageQueue 方法调用WebViewJavascriptBridge._fetchQueue() 这个方法去sendMessageQueue这个数组获取消息内容字典,大致流程已经解析完,最后在来看看流程图,梳理下流程吧

4887王中王鉄算盘奖结果 20

最后总结下原来这个库原来很简单只是通过iframe 设置src变化去触发app 的webView 的代理回调,但是使用这个库却察觉不到这个痕迹,使用十分简单,代码逻辑也十分整洁,还是十分推荐的。

本文由4887王中王鉄算盘奖结果发布于操作系统,转载请注明出处:4887王中王鉄算盘奖结果看OC和JS交互过程,源码解

关键词:

上一篇:没有了

下一篇:没有了

最火资讯