WebJavaScriptBridge原始碼解讀
原文地址 : ofollow,noindex">https://lm1024.xyz/archives/59
1、這個庫解決的問題
以一種優雅的方式,解決OC與UIWebView(WKWebView)上js互動問題
2、實現原理的核心方法 :
- UIWebView
//通過改方法oc程式碼向webView註冊js方法 - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script; //通過該方法,攔截js回撥oc的請求 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- WKWebView
//通過改方法oc程式碼向webView註冊js方法 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler; //通過該方法,攔截js回撥oc的請求 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
3、約定的協議:
kNewProtocolScheme @"https"
kQueueHasMessage @" wvjb_queue_message "
kBridgeLoaded @" bridge_loaded "
webview會向oc傳送上述三種類型的request請求。 oc端撲捉到對應的請求後通過協議型別來使用不同的方式處理。
4、技術點:
- oc向js註冊一個方法後,js呼叫oc的方法怎麼把處理好的資料回撥給js。(直白點就是:對方呼叫了我的方法,我處理完資料之後。怎麼把我處理後的資料回撥給對方)
- js和oc物件的資料格式互換。
5、閱讀釋義:此處以oc的角度來釋義
- handlerCallBack: 指的是js程式碼呼叫oc向js註冊的方法時,oc端執行的回撥。
- resposeCallBack:
1)resposeCallBack 作為引數包含在 handlerCallBack中的時候,指的是js呼叫oc方法,oc端處理資料完畢後,將資料傳遞給js端的回撥。
2)resposeCallBack 作為- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback
方法的引數時候,指的是oc呼叫js程式碼後,js處理完資料給oc的回撥。
6、執行流程圖:

129508BD539FAC2E3E66AE5CA3C64B38.jpg
7、各層職責
- WebViewJavascriptBridge, WKWebViewJavascriptBridge:負責oc端和js端的互動。將oc端的生成的訊息橋接給js端,同時也負責捕獲js端傳送給oc端的訊息。
- WebViewJavascriptBridgeBase:訊息處理分發中心。WebViewJavascriptBridge和WKWebViewJavascriptBridge 接受到oc的傳送訊息的命令後,生成對應的js訊息格式返回給WebViewJavascriptBridge和WKWebViewJavascriptBridge註冊至js端。 同時解析WebViewJavascriptBridge和WKWebViewJavascriptBridge捕獲js的訊息後解析訊息然後回撥oc端對應的方法。
8、核心程式碼:
- (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]; }
- 該方法oc呼叫js的方法時負責生成對應的訊息格式,然後分發給WebViewJavascriptBridge或者WKWebViewJavascriptBridge執行對應的js程式碼
- (void)flushMessageQueue:(NSString *)messageQueueString{ if (messageQueueString == nil || messageQueueString.length == 0) { NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page."); return; } id messages = [self _deserializeMessageJSON:messageQueueString]; for (WVJBMessage* message in messages) { if (![message isKindOfClass:[WVJBMessage class]]) { NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message); continue; } [self _log:@"RCVD" json:message]; NSString* responseId = message[@"responseId"]; if (responseId) { WVJBResponseCallback responseCallback = _responseCallbacks[responseId]; responseCallback(message[@"responseData"]); [self.responseCallbacks removeObjectForKey:responseId]; } else { 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); } } }
- 這段程式碼在WebViewJavascriptBridge或者WKWebViewJavascriptBridge捕捉到js呼叫oc程式碼的訊息後解析訊息然後找到對應的oc回撥block,回撥給oc。
9、巧妙點:
1)、在閱讀之前我是非常好奇,在這個庫裡面是怎麼處理oc的responseCallBack和js回撥callBackFunction 是怎麼相互轉換的。閱讀之後確實感覺作者這樣處理的方式真的十分巧妙。作者在oc 和 js端都維護了responseCallbacks和messageHandlers兩個字典,每次生成message都會給該message生成一個id,這樣兩邊通訊的時候就能通過message id來獲取各自平臺的 對應的回撥方法。這樣就直接把oc端和js端給隔離開了,兩端各自幹各自的事情互不關心。
10、小瑕疵:
在WebViewJavascriptBridge中的下面程式碼中
+ (instancetype)bridge:(id)webView { #if defined supportsWKWebView if ([webView isKindOfClass:[WKWebView class]]) { return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView]; } #endif if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) { WebViewJavascriptBridge* bridge = [[self alloc] init]; [bridge _platformSpecificSetup:webView]; return bridge; } [NSException raise:@"BadWebViewType" format:@"Unknown web view type."]; return nil; }
看到這麼一段程式碼
if ([webView isKindOfClass:[WKWebView class]]) { return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView]; }
瞬間有一種跳戲的感覺,初始化的一個B然而卻用A的引用指向B。雖然理解作者的意圖是想相容WKWebView。但是這樣寫可能會給上層業務層埋下一個坑,例如:使用者在不知情的情況下用categroy給 WebViewJavascriptBridge
新加了一個方法,使用者最初使用的UIWebView,那麼一點問題也沒有。後來遷移到wkWebView後,呼叫category裡面的方法就會閃退。
11、個人覺得改進點:
1、WKWebViewJavascriptBridge和WebViewJavascriptBridge的.h檔案裡面的內容幾乎一模一樣,是否可以抽成一個基類或者介面,這樣上層使用的時候就不必要關係我用的是WKWebViewJavascriptBridge還是WebViewJavascriptBridge建立的物件。
2、既然要根據webView的型別來初始化不同bridge物件,那麼抽出一個工廠方法是不是好一點。雖然 + (instancetype)bridge:(id)webView)
是有工廠化方法的意思,但是卻埋了一個坑。