iOS源码解析—WebViewJSBridge
简介
一个OC和JS交互的桥接机制,主要包含3个类,JS端window.WebViewJavascriptBridge,OC端WebViewJavascriptBridge和WebViewJavascriptBridgeBase。桥接类支持JS调用OC方法,OC调用JS方法。JS调用OC通过重定向url并取handlerName来调用,OC调用JS通过stringByEvaluatingJavaScriptFromString调用。
-
OC端初始化、注册func:
-
通过初始化方法生成1个WebViewJavascriptBridge以及WebViewJavascriptBridgeBase。webview的delegate改为WebViewJavascriptBridge,WebViewJavascriptBridge的_webViewDelegate是外部传入的webviewController。
+ (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle { WebViewJavascriptBridge* bridge = [[self alloc] init]; [bridge _platformSpecificSetup:webView webViewDelegate:webViewDelegate handler:messageHandler resourceBundle:bundle]; return bridge; } - (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(id<UIWebViewDelegate>)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle { _webView = webView; _webView.delegate = self; _webViewDelegate = webViewDelegate; _base = [[WebViewJavascriptBridgeBase alloc] initWithHandler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle]; _base.delegate = self; }
-
messageHandler是默认处理函数,当JS调用OC时,根据方法名找不到OC的block时,或者通过send()方法调用OC时,调用该默认messageHandler。
-
messageHandlers是键值对,存放了OC注册的方法以及对应的方法名。startupMessageQueue是数组,存放了JS端WebViewJavascriptBridge.js.txt脚本执行之前,OC调用的JS方法信息message。
-(id)initWithHandler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle { self = [super init]; _resourceBundle = bundle; self.messageHandler = messageHandler; self.messageHandlers = [NSMutableDictionary dictionary]; self.startupMessageQueue = [NSMutableArray array]; self.responseCallbacks = [NSMutableDictionary dictionary]; _uniqueId = 0; return(self); }
-
注册OC方法,调用registerHandler:name handler:(WVJBHandler)handler,往messageHandlers键值对中存。
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler { _base.messageHandlers[handlerName] = [handler copy]; }
-
-
JS端初始化、注册func
-
加载JS脚本
webView加载完之后,进入webViewDidFinishLoad回调,调用injectJavascriptFile,执行WebViewJavascriptBridge.js脚本。并且把startupMessageQueue中的message批量处理掉。
- (void)injectJavascriptFile:(BOOL)shouldInject { ... NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"]; NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; [self _evaluateJavascript:js]; [self dispatchStartUpMessageQueue]; } - (void)dispatchStartUpMessageQueue { if (self.startupMessageQueue) { for (id queuedMessage in self.startupMessageQueue) { [self _dispatchMessage:queuedMessage]; } self.startupMessageQueue = nil; } }
-
WebViewJavascriptBridge.js脚本创建window.WebViewJavascriptBridge对象。_handleMessageFromObjC是OC调用JS的入口。
window.WebViewJavascriptBridge = { init: init, send: send, registerHandler: registerHandler, callHandler: callHandler, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC }
-
同时创建一些变量,receiveMessageQueue是数组,存放了init方法调用之前,OC调用JS的信息Message队列。messageHandlers是键值对,存放JS端注册的方法。sendMessageQueue是Message队列,存放JS调用OC的方法。
var messagingIframe var sendMessageQueue = [] var receiveMessageQueue = [] var messageHandlers = {}
-
注册JS响应方法,调用registerHandler方法注册。
function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler }
-
init方法初始化,同时messageHandler是初始化完的回调block,处理默认逻辑,和OC的messageHandler功能一样。同时批量处理receiveMessageQueue中的消息。
function init(messageHandler) { WebViewJavascriptBridge._messageHandler = messageHandler var receivedMessages = receiveMessageQueue receiveMessageQueue = null for (var i=0; i<receivedMessages.length; i++) { _dispatchMessageFromObjC(receivedMessages[i]) } }
-
-
OC端调用JS端func:
-
调用callHandler:handlerName data:data responseCallback:responseCallback方法或者send:data responseCallback:responseCallback方法来调用JS方法。内部调用WebViewJavascriptBridgeBase的sendData:data responseCallback:responseCallback handlerName:handlerName方法。
- (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]; }
-
如果responseCallback,则创建一个responseCallback,维护到responseCallbacks键值对中。包装一个Message对象,包括data、handlerName和callbackId,一起发送。如果WebViewJavascriptBridge.js未加载,则往startupMessageQueue队列里面添加。如果已加载,则发送给window.WebViewJavascriptBridge对象。
- (void)_queueMessage:(WVJBMessage*)message { if (self.startupMessageQueue) { [self.startupMessageQueue addObject:message]; } else { [self _dispatchMessage:message]; } } - (void)_dispatchMessage:(WVJBMessage*)message { ... NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON]; [self _evaluateJavascript:javascriptCommand]; }
-
执行_handleMessageFromObjC方法,如果WebViewJavascriptBridge未init初始化,则添加到receiveMessageQueue队列,否则执行dispatchMessageFromObjC。
function _handleMessageFromObjC(messageJSON) { if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON) } else { _dispatchMessageFromObjC(messageJSON) } }
-
dispatchMessageFromObjC,如果有callBackId,则创建一个回调func,通过doSend回调OC,如果没有handlerName,则调用默认的messageHandler,传data。
function _dispatchMessageFromObjC(messageJSON) { 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] } handler(message.data, responseCallback) }
-
处理回调,js方法调用responseCallback(),传入responseId和responseData,调用doSend方法。doSend方法首先构建一个Message,放入sendMessageQueue中,然后重定向一下scheme://message。
function _doSend(message, responseCallback) { ... sendMessageQueue.push(message) messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE }
-
OC端的WebViewJavascriptBridge拦截到url,获取sendMessageQueue数组中的message,执行flushMessageQueue:message方法。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { ... NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]; [_base flushMessageQueue:messageQueueString]; ... }
-
flushMessageQueue:message方法里判断,如果有responseId,则通过responseId从_responseCallbacks键值对中取出OC的回调方法,并且把responseData传给回调方法。
- (void)flushMessageQueue:(NSString *)messageQueueString{ for (WVJBMessage* message in messages) { NSString* responseId = message[@"responseId"]; if (responseId) { WVJBResponseCallback responseCallback = _responseCallbacks[responseId]; responseCallback(message[@"responseData"]); } } }
-
-
JS端调用OC端func:
-
JS调用send和callHandler,然后调用doSend()方法
_doSend({ handlerName:handlerName, data:data }, responseCallback)
-
_doSend方法中,如果有回调responseCallback,则维护回调responseCallbacks,存放responseCallback。sendMessageQueue里添加message。然后重定向Url。
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 }
-
OC端的shouldStartLoadWithRequest:方法拦截到url,通过WebViewJavascriptBridge._fetchQueue()获取message队列,然后调用flushMessageQueue方法处理message。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]; [_base flushMessageQueue:messageQueueString]; return NO; }
-
flushMessageQueue:messageQueue方法,如果有callbackId,则调用_queueMessage方法,传递回调数据以及responseId。如果有handlerName,则调用相应的OC方法,否则调用默认方法messageHandler。
- (void)flushMessageQueue:(NSString *)messageQueueString{ NSString* callbackId = message[@"callbackId"]; if (callbackId) { responseCallback = ^(id responseData) { if (responseData == nil) { responseData = [NSNull null]; } WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData }; [self _queueMessage:msg]; }; } WVJBHandler handler; if (message[@"handlerName"]) { handler = self.messageHandlers[message[@"handlerName"]]; } else { handler = self.messageHandler; } handler(message[@"data"], responseCallback); }
-
_queueMessage方法,调用dispatchMessage方法,接着调用WebViewJavascriptBridge.handleMessageFromObjC方法。
- (void)_dispatchMessage:(WVJBMessage*)message { NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON]; [self _evaluateJavascript:javascriptCommand]; }
-
_handleMessageFromObjC调用dispatchMessageFromObjC(messageJSON)方法,如果message有responseId,则通过responseId和responseCallbacks找到JS回调方法,执行。
function _dispatchMessageFromObjC(messageJSON) { if (message.responseId) { responseCallback = responseCallbacks[message.responseId] if (!responseCallback) { return; } responseCallback(message.responseData) delete responseCallbacks[message.responseId] } }
-
结尾
WebViewJavascriptBridge提供了一种native和js交互的思路,且十分轻量。