iOS我爱编程iOS知识搜集

OC与JS交互之 WebViewJavascriptBridge

2018-03-29  本文已影响189人  大良造L

导语

本文将讲解 UIWebView 和 WKWebView 通过 WebViewJavascriptBridge 三方库进行通信的原理;梳理OC -- > JS, JS --> OC 调动交互的流程;和OC方、JS方各自做了具体什么操作才使Native与H5有了交互的功能。本文并不会去讲解 WebViewJavascriptBridge 库的使用和方法的讲解,你可以通过以下参考内容去了解。

OC 调用 JS

oc 调用 js

OC 相关代码: 调用与JS商量好的 oc_to_js_js’registerFunctionName 函数

[_webViewBridge callHandler:@“oc_to_js_js’registerFunctionName” data:@"oc传给js的数据” responseCallback:^(id responseData) {
        NSLog(@"JS执行完回调给OC的数据:%@“,responseData);
    }];

JS 相关代码: 注册一个叫 oc_to_js_js’registerFunctionName 的函数

setupWebViewJavascriptBridge(function(bridge) {
                                 bridge.registerHandler('oc_to_js_js’registerFunctionName', function(data, responseCallback) {
                                                        alert(‘oc成功调用js,并打印数据data:’+data);
                                                        responseCallback(‘js执行完回调给OC的数据');
                                                        })
                                 })

如上OC和JS代码可以看出,OC需要调用JS注册的‘ oc_to_js_js’registerFunctionName’ 方法,并传递数据data给JS;JS在收到OC的消息后alert了传递过来的数据,并回传了一份数据给OC。
那么这个 oc--data--js--data--oc 的流程到底是怎样的呢
大致步骤:

注入JS代码
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
   ...
   if ([_base isQueueMessageURL:navigationAction.request.URL]) {
         [webView stringByEvaluatingJavaScriptFromString:@“js代码”]
    } 
   ...  
}
保存js注册的函数
function registerHandler(handlerName, handler) {
   messageHandlers[handlerName] = handler;
}
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
    NSMutableDictionary* message = [NSMutableDictionary dictionary];
    if (data) {
        message[@"data"] = data;
    }
    if (responseCallback) {
        NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
        self.responseCallbacks[callbackId] = [responseCallback copy];
        message[@"callbackId"] = callbackId;
    }
    if (handlerName) {
        message[@"handlerName"] = handlerName;
    }
    [self _queueMessage:message];
}
- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
  
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
}
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
dispatch_sync(dispatch_get_main_queue(), ^{
           [webView stringByEvaluatingJavaScriptFromString:javascriptCommand]
        });
取出 callback 函数和 handler 函数执行调用,在handler中执行 callback 数据回传操作。
responseCallback = responseCallbacks[message.responseId];
var handler = messageHandlers[message.handlerName];
if (handler)  {
    handler(message.data, responseCallback);
}

JS 调用 OC

js 调用 oc

JS调用OC 一般会有三种方式

本文只要讲解第二种方式,通过Scheme去实现一个 JS 到 Native 传递消息的一个过程。

OC 代码 : 注册一个叫 js_to_oc_oc’registerFunctionName 的方法

[webViewBridge registerHandler:@"js_to_oc_oc’registerFunctionName” handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@“js成功调用oc, 并获取数据data”);
        responseCallback(@“oc回调给js的数据 如:支付失败”);
    }];

JS 代码 : 调用与OC商量好的 js_to_oc_oc’registerFunctionName 方法

WebViewJavascriptBridge.callHandler('js_to_oc_oc’registerFunctionName', "{\"content\" : \"js传递给oc的数据内容\”}”, function(response) {
                                            alert('oc成功被调用,并回传给js的回调数据:’ + response);
                                            document.getElementById("returnValue").value = response;
                                            });

如上OC和JS方法可以看出, JS需要调用OC注册的‘ js_to_oc_oc’registerFunctionName’ 方法,并传递数据json字符串给OC;OC在收到JS的消息后log了传递过来的数据,并回传了一份数据给JS。
大致步骤:

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}
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);
}
最开始注入的js代码中,会对当前html添加一个<iframe>, 并设置为隐性
iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(iframe);
重新赋值iframe的src
iframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
    ...  
    if ([_base isCorrectProcotocolScheme:url]) {
         ...
         // js 跳转,拦截处理
        decisionHandler(WKNavigationActionPolicyCancel); 
    }else {
        decisionHandler(WKNavigationActionPolicyAllow); 
    }
}
[webView evaluateJavaScript:@"WebViewJavascriptBridge._fetchQueue();" completionHandler:^(NSString* result, NSError* error) {
   // result 为获取到的函数信息(以 handlerName、data、callback) 为键的json字符串
}];
js 函数
function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
}
WVJBResponseCallback responseCallback = _responseCallbacks[message[@"responseId"]]; // 获取存储的callback函数
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];  // 获取存储的handler函数
 if (!handler) {
     NSLog(@"oc环境中没有注册过此方法");
    continue;
 }     
handler(message[@"data"], responseCallback);

总结

关于 js 与 oc 交互的更多细节,可以自己查看WebViewJavascriptBridge的源码 https://github.com/marcuswestin/WebViewJavascriptBridge
最后附上一段一开始被注入到webView中的JS代码
if (window.WebViewJavascriptBridge) {
    return;
}

if (!window.onerror) {
    window.onerror = function(msg, url, line) {
        console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
    }
}
window.WebViewJavascriptBridge = {
    registerHandler: registerHandler,
    callHandler: callHandler,
    disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
    _fetchQueue: _fetchQueue,
    _handleMessageFromObjC: _handleMessageFromObjC
};

var messagingIframe;
var sendMessageQueue = [];
var messageHandlers = {};

var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme';
var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__';

var responseCallbacks = {};
var uniqueId = 1;
var dispatchMessagesWithTimeoutSafety = true;

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

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

function disableJavscriptAlertBoxSafetyTimeout() {
    dispatchMessagesWithTimeoutSafety = false;
}

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;
}

function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
}

function _dispatchMessageFromObjC(messageJSON) {
    if (dispatchMessagesWithTimeoutSafety) {
        setTimeout(_doDispatchMessageFromObjC);
    } else {
        _doDispatchMessageFromObjC();
    }

    function _doDispatchMessageFromObjC() {
        var message = JSON.parse(messageJSON);
        var messageHandler;
        var responseCallback;

        if (message.responseId) {
            responseCallback = responseCallbacks[message.responseId];
            if (!responseCallback) {
                return;
            }
            responseCallback(message.responseData);
            delete responseCallbacks[message.responseId];
        } else {
            if (message.callbackId) {
                var callbackResponseId = message.callbackId;
                responseCallback = function(responseData) {
                    _doSend({
                        handlerName: message.handlerName,
                        responseId: callbackResponseId,
                        responseData: responseData
                    });
                };
            }

            var handler = messageHandlers[message.handlerName];
            if (!handler) {
                console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
            } else {
                handler(message.data, responseCallback);
            }
        }
    }
}

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() {
    var callbacks = window.WVJBCallbacks;
    delete window.WVJBCallbacks;
    for (var i = 0; i < callbacks.length; i++) {
        callbacks[i](WebViewJavascriptBridge);
    }
}
}
上一篇下一篇

猜你喜欢

热点阅读