WebViewJavascriptBridge 原理解析

701 阅读3分钟

WebViewJavascriptBridge是项目中常用的OC与js交互的第三方框架,它没有通过苹果的JavascriptCore框架来实现,而是实现了自己的逻辑。

WebViewJavascriptBridgeBase是OC模块的功能实现类,对应的js功能类为WebViewJavascriptBridge_JS,此处巧妙地使用字符串来解决了之前版本集成打包需要bundle加载js模块的问题。

先说明几个数据结构:

  • @property (strong, nonatomic) NSMutableDictionary messageHandlers;,以字典的形式保存注册的Hander,js相似数据结构为:var messageHandlers = {};
  • NSMutableDictionary* responseCallbacks;,以字典的形式保存调用Handler后回调给js消息的Block,js相似数据结构为:var responseCallbacks = {};

下面看一下Message的数据结构,当然在传送的过程中是一JSON的格式进行传送的,以OC为例:

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

data:要传递的数据(如参数)。

callbackId: 唯一标示作为responseCallBack的Key

handerName:Handler唯一标示(Key)

对应js代码:

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

发送消息,OC为调用WebView调用evaluateJavascript方法来调用js,通过WebViewJavascriptBridge来调用js的_handleMessageFromObjC方法主要代码:

- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
    <-------------省略-------------->
    
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

而js调用OC则是通过WebView加载Request来实现:

 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;
	}
  1. 添加Message到待处理消息集合MessageQueue,然后调用messagingIframe.src来加载Request,格式为:https://__wvjb_queue_message__
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

2.获取要处理的js消息,进行处理,先说一下代码里的判断函数

isWebViewJavascriptBridgeURL:判断是否是Bridge处理的URL格式,为了兼容旧版本,判断了2个Scheme,然后继续下面2个函数的判断。

isBridgeLoadedURL:判断是否是Bridge初始化加载URL格式,对应格式为:https://__bridge_loaded__,如果是,则调用injectJavascriptFile函数,加载初始化的js文件(WebViewJavascriptBridge_JS)

isQueueMessageURL:判断是否是Bridge发送消息的URL格式,对应格式为:https://__wvjb_queue_message__

当url是https://__wvjb_queue_message__,则表示js有消息要处理,WebView加载Request,调用OC[self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]这个方法来调用js文件中的

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

方法,来获取要处理的消息,然后调用OC的flushMessageQueue:方法来处理消息

处理消息,OC处理js方法为- (void)flushMessageQueue:(NSString *)messageQueueString{xxx}

先看这部分代码:

        WVJBResponseCallback responseCallback = NULL;
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            
            handler(message[@"data"], responseCallback);

如果存在callbackId,则代表js存在responseCallBack,需要OC回调,所以需要发送回调消息即`WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData }; [self _queueMessage:msg];只是callbackId的KEY设置成responseId来区分是回调。 再看代码:

 NSString* responseId = message[@"responseId"];
        if (responseId) {
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        }

首先判断是否有responseId(即OC的ResponseCallBack保存时候的CallbackId),如果有,那么代表这个消息是js的回调消息(OC调用js注册的Handler),则直接处理。

如果我的文章对你有所帮助,请留言告诉我,Thanks!