WebViewJavascriptBridge解析

前序

本文csdn地址:http://blog.csdn.net/game3108/article/details/51147923
iOS原生應(yīng)用和web頁(yè)面的交互主要有:JavaScriptCore(iOS7以后)與攔截協(xié)議兩個(gè)方法照卦。

因?yàn)槲覀兊腶pp要兼容iOS6,所以我們?cè)趙eb js和native交互使用的是攔截協(xié)議的一個(gè)很有名的第三方框架:WebViewJavascriptBridge珊燎,本文從源代碼來(lái)解析一下WebViewJavascriptBridge的工作方式享言。
(對(duì)于iOS8新出的WKWebView,原理方式相同,會(huì)稍作提及拓巧,但不會(huì)詳細(xì)展開(kāi)环壤。)

iOS web和native交互的方式

首先拋開(kāi)WebViewJavascriptBridge,思考一個(gè)問(wèn)題,如果我們自己去做一套js與native交互的輪子诉濒,應(yīng)該如何去做周伦?

  • 尋找js與native可能交互的接口
  • 設(shè)計(jì)一套融合交互接口的數(shù)據(jù)模式
  • 完善整體輪子

我們來(lái)依此分析每一項(xiàng)

尋找js與native可能交互的接口

查詢相應(yīng)的文檔可以得知,在UIWebView中未荒,native有直接調(diào)用js的方法,js沒(méi)有直接調(diào)用native的方法

native直接調(diào)用js的方法:

- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;//WKWebView使用专挪,以下類推)

那對(duì)于js來(lái)說(shuō),無(wú)法直接調(diào)用native代碼是否表示無(wú)法進(jìn)行交互片排?

答案是否定的寨腔。雖然無(wú)法直接調(diào)用native代碼,但是iOS的接口中還是設(shè)計(jì)了可以通過(guò)間接的方式傳遞js調(diào)用的消息

js間接調(diào)用native的方式:
對(duì)于iOS UIWebView熟悉的人來(lái)說(shuō)率寡,

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

這個(gè)方法迫卢,可以每次在UIWebView進(jìn)行重定向URL的時(shí)候,進(jìn)行觸發(fā)冶共,只要把一個(gè)js調(diào)用native的方法包裝成一個(gè)重定向URL,就可以在本地接收到相應(yīng)的方法乾蛤。

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

小結(jié):
native可以直接調(diào)用js,js將方法包裝成重定向請(qǐng)求,使得native截取并分析捅僵,執(zhí)行相應(yīng)代碼家卖。

問(wèn)題:那就是說(shuō),js代碼每次把一次調(diào)用里面所有參數(shù)都集合進(jìn)一個(gè)重定向請(qǐng)求里直接讓本地截取和執(zhí)行庙楚?

設(shè)計(jì)一套融合交互接口的數(shù)據(jù)模式

在js與native交互的時(shí)候上荡,設(shè)計(jì)一套兩邊都可以使用的模式十分關(guān)鍵。

native代碼與交互js代碼明顯是異步的一個(gè)操作醋奠,尤其是在雙方初始化的時(shí)候榛臼,native代碼無(wú)法確定js代碼是否加載完成伊佃,如果在js未加載完成的時(shí)候進(jìn)行相應(yīng)方法調(diào)用,是沒(méi)有效果的沛善。
所以在雙方交互的時(shí)候航揉,設(shè)計(jì)一個(gè)list數(shù)組,去存儲(chǔ)兩遍在未初始化完成前需要執(zhí)行的方法金刁,十分重要帅涂。

而對(duì)于每一個(gè)方法請(qǐng)求,我們至少需要以下參數(shù):

  • 方法名
  • 方法參數(shù)
  • 回調(diào)

在此基礎(chǔ)上尤蛮,key-value對(duì)(Dictionary)會(huì)是一個(gè)很好的選擇媳友。

小結(jié):交互設(shè)計(jì)上,兩遍都會(huì)初始化一個(gè)list去存儲(chǔ)未初始化時(shí)候?qū)Ψ降姆椒ㄕ?qǐng)求产捞。
而對(duì)于每一個(gè)方法請(qǐng)求醇锚,通過(guò)key-value對(duì),去提供雙方解析坯临。

完善整體輪子

有了交互的接口焊唬,還有數(shù)據(jù)格式,雙方獲取到對(duì)方的方法調(diào)用進(jìn)行處理就不再困難看靠。
在其中赶促,唯一還有些繞的是雙方的回調(diào)。
js調(diào)用native的回調(diào)挟炬,可以在native中直接通過(guò)調(diào)用js的方式鸥滨,進(jìn)行回調(diào)函數(shù)的調(diào)用。
而native調(diào)用js的回調(diào)谤祖,還是要通過(guò)native截取js的方式婿滓,進(jìn)行回調(diào)函數(shù)的調(diào)用。
所以雙方都需要把本地的回調(diào)函數(shù)通過(guò)key-value對(duì)(Dictionary)存儲(chǔ)下來(lái)泊脐。

那在整體設(shè)計(jì)上空幻,雙方的方法解析與互相調(diào)用也應(yīng)該分離開(kāi)來(lái)烁峭,由此可以達(dá)到代碼模塊化的目的容客。

小結(jié):在完善輪子的時(shí)候,根據(jù)設(shè)計(jì)模式的原則约郁,進(jìn)行相應(yīng)的模塊化缩挑,方便代碼復(fù)用。

解析WebViewJavascriptBridge的源代碼

上一章我們整體設(shè)計(jì)了我們自己的一個(gè)native與js交互的輪子鬓梅,WebViewJavascriptBridge本身的做法也是類似供置,現(xiàn)在我們解析WebViewJavascriptBridge的源代碼來(lái)了解它是如何做到這每一步。
我們將js調(diào)用native的整個(gè)流程走一遍绽快,就可以完全清楚WebViewJavascriptBridge的邏輯芥丧。

首先是在頁(yè)面加載完成的時(shí)候紧阔,native會(huì)注入一段js代碼:

- (void)webViewDidFinishLoad:(UIView<FLWebViewProvider> *)webView {
    if (webView != _webView) { return; }
    _numRequestsLoading--;
    if (_numRequestsLoading == 0 && ![[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:[_base webViewJavascriptCheckCommand]] isEqualToString:@"true"]) {
        [_base injectJavascriptFile:YES webView:webView];    }
    [_base dispatchStartUpMessageQueue];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
        [strongDelegate webViewDidFinishLoad:(UIWebView *)webView];
    }
}

其中

[_base injectJavascriptFile:YES webView:webView]

- (void)injectJavascriptFile:(BOOL)shouldInject webView:(UIView<FLWebViewProvider> *)webview {
    if(shouldInject){
        NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
        NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
        NSString *jsBridge = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
        [self webview:webview evaluateJavaScript:jsBridge completionHandler:^(id callback, NSError *error) {
            [self injectForDispatch:webview];
        }];
    }
}

將本地端的WebViewJavascriptBridge.js.txt注入到了web頁(yè)面的js代碼中

WebViewJavascriptBridge初始化時(shí)候,本身提供了ExampleApp.html頁(yè)面
我們從項(xiàng)目中找處一句js調(diào)用native的語(yǔ)句:

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

我們找到WebViewJavascriptBridge.js.txt

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)
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
    }

其中续担,正如我們自己設(shè)計(jì)的擅耽,
首先,我們會(huì)將請(qǐng)求分為3個(gè)部分

  • handlerName 方法名
  • data 方法參數(shù)
  • callback 回調(diào)

用dictionary的方式存儲(chǔ)他們物遇,組成了message乖仇,message本身就是我們發(fā)起的請(qǐng)求
而responseCallbacks通過(guò)特殊的callbackid,存儲(chǔ)下了這一次的函數(shù)回調(diào)
message增加了一個(gè)’callbackid’的參數(shù)询兴,去存儲(chǔ)這個(gè)callbackid

上面都是我們?cè)O(shè)計(jì)的方式乃沙,就是比較特殊的地方是,
WebViewJavascriptBridge將整個(gè)請(qǐng)求message诗舰,塞入了sendMessageQueue中警儒,而并非我們想當(dāng)然的塞入url重定向中。
然后使用messagingIframe發(fā)起重定向眶根。

function _createQueueReadyIframe(doc) {
        messagingIframe = doc.createElement('iframe')
        messagingIframe.style.display = 'none'
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
        doc.documentElement.appendChild(messagingIframe)
    }
var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'
var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

而這個(gè)重定向url由CUSTOM_PROTOCOL_SCHEME和QUEUE_HAS_MESSAGE組成冷蚂。

從這里可以看出,js調(diào)用本地代碼汛闸,并沒(méi)有直接將參數(shù)請(qǐng)求塞入重定向url蝙茶,而是塞入了一個(gè)list中,而所有的參數(shù)請(qǐng)求都是發(fā)了同一個(gè)诸老。

再看本地代碼隆夯,之前所說(shuō)的解析代碼:

本地 WebViewJavascriptBridge.m

- (BOOL)webView:(UIView<FLWebViewProvider> *)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 isCorrectProcotocolScheme:url]) {
        if ([_base isCorrectHost:url]) {
            NSString *messageQueueString = [(UIWebView*)webView stringByEvaluatingJavaScriptFromString:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:(UIWebView *)webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

其中_base是一個(gè)WebViewJavascriptBridgeBase對(duì)象,它包含了:

-(BOOL)isCorrectProcotocolScheme:(NSURL*)url {
    if([[url scheme] isEqualToString:kCustomProtocolScheme]){
        return YES;
    } else {
        return NO;
    }
}
-(BOOL)isCorrectHost:(NSURL*)url {
    if([[url host] isEqualToString:kQueueHasMessage]){
        return YES;
    } else {
        return NO;
    }
}
#define kCustomProtocolScheme @"wvjbscheme"

#define kQueueHasMessage @"__WVJB_QUEUE_MESSAGE__"

其中,很顯然别伏,通過(guò)檢查scheme和host蹄衷,就可以清楚的知道,這個(gè)請(qǐng)求是不是WebViewJavascriptBridge的重定向請(qǐng)求厘肮。

NSString *messageQueueString = [(UIWebView*)webView stringByEvaluatingJavaScriptFromString:[_base webViewJavascriptFetchQueyCommand]];

很明顯是在獲得之前sendMessageQueue

查找WebViewJavascriptBridgeBase.m和WebViewJavascriptBridge.js.txt文件

-(NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}
function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue)
    sendMessageQueue = []
    return messageQueueString
}

那這邊我們就了解了js調(diào)用本地端的方法:
js發(fā)起一個(gè)特殊的url請(qǐng)求愧口,告訴本地端,我發(fā)起請(qǐng)求了类茂。并且存儲(chǔ)相應(yīng)的回調(diào)函數(shù)和消息隊(duì)列耍属。
而本地端接收到消息,會(huì)去主動(dòng)拉取js中存儲(chǔ)消息的隊(duì)列巩检。

之后就是處理消息的方式:

[_base flushMessageQueue:messageQueueString];

找到WebViewJavascriptBridgeBase.m

- (void)flushMessageQueue:(NSString *)messageQueueString{
    id messages;
    if (messageQueueString) {
        messages = [self _deserializeMessageJSON:messageQueueString];
    }
    if (![messages isKindOfClass:[NSArray class]]) {
        NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);
        return;
    }
    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;
            if (message[@"handlerName"]) {
                handler = self.messageHandlers[message[@"handlerName"]];
            } else {
                handler = self.messageHandler;
            }
            if (!handler) {
//                [NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];
                cootek_log(@"No handler for message from JS: %@",message);
            } else {
                handler(message[@"data"], responseCallback);
            }
        }
    }
}

首先厚骗,解析messages

messages = [self _deserializeMessageJSON:messageQueueString];
- (NSArray*)_deserializeMessageJSON:(NSString *)messageJSON {
    return [NSJSONSerialization JSONObjectWithData:[messageJSON dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
}

將他們轉(zhuǎn)回相應(yīng)的存儲(chǔ)Dictionary的list(NSArray)
然后遍歷每一個(gè)消息:

for (WVJBMessage* message in messages)

其中先判斷了:

NSString* responseId = message[@"responseId"];

我們之前發(fā)起的請(qǐng)求的dictionary,只包含 handlerName兢哭,data领舰, 和可能有的callbackId
所以我們一定是走else語(yǔ)句

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

而這部分代碼的邏輯,就是如果存在callbackid(表明存在callback),就去設(shè)置一個(gè)block responseCallback冲秽,提供回調(diào)

WVJBHandler handler;
            if (message[@"handlerName"]) {
                handler = self.messageHandlers[message[@"handlerName"]];
            } else {
                handler = self.messageHandler;
            }
            if (!handler) {
//                [NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];
                cootek_log(@"No handler for message from JS: %@",message);
            } else {
                handler(message[@"data"], responseCallback);
            }

這部分就是實(shí)際的函數(shù)調(diào)用舍咖,將相應(yīng)的handler取出,并且進(jìn)行調(diào)用

handler(message[@"data"], responseCallback);

由其可見(jiàn)锉桑,我們本地端必須先要在self.messageHandlers中谎仲,包含這樣的一個(gè)消息,才有可能進(jìn)行執(zhí)行刨仑,所以郑诺,本地端必須先注冊(cè)相應(yīng)的方法。
即:

[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {  
    NSLog(@"testObjcCallback called: %@", data);  
    responseCallback(@"Response from testObjcCallback");  
}]; 
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}

首先要在bridge中注冊(cè)了testObjcCallback方法杉武,才會(huì)去執(zhí)行到這段代碼辙诞。

即,一開(kāi)始轻抱,在本地端飞涂,你必須先registerHandler,將相應(yīng)的block隊(duì)贏的handlername注冊(cè)到_base.messageHandlers中祈搜,表示存在這樣的方法较店,
然后當(dāng)你在js中callHandler的時(shí)候,就會(huì)通過(guò)一系列調(diào)用容燕,找到這個(gè)handler方法梁呈,并且最后執(zhí)行它。
而在執(zhí)行block的函數(shù)的時(shí)候蘸秘,也會(huì)包含
responseCallback(@"Response from testObjcCallback");
進(jìn)行回調(diào)的執(zhí)行
回到回調(diào)的定義:

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

如果回調(diào)存在官卡,就會(huì)組成這樣一條msg

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };

包含了兩個(gè)key, responseid與responseData

[self _queueMessage:msg];
- (void)_queueMessage:(WVJBMessage*)message {
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        [self _dispatchMessage:message];
    }
}

走到[self _dispatchMessage:message]函數(shù)
(self.startupMessageQueue的目的是存儲(chǔ)沒(méi)有初始化時(shí)候的方法,提供之后調(diào)用)

- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message];
    [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];
    if ([[NSThread currentThread] isMainThread]) {
        [_flWebView evaluateJavaScript:javascriptCommand completionHandler:nil];
    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [_flWebView evaluateJavaScript:javascriptCommand completionHandler:nil];
        });
    }
}

調(diào)用到j(luò)s的_handleMessageFromObjC方法

function _handleMessageFromObjC(messageJSON) {
        if (receiveMessageQueue) {
            receiveMessageQueue.push(messageJSON)
        } else {
            _dispatchMessageFromObjC(messageJSON)
        }
    }

走到_dispatchMessageFromObjC(messageJSON)函數(shù)
(receiveMessageQueue的目的是存儲(chǔ)沒(méi)有初始化時(shí)候的方法,提供之后調(diào)用)

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

這里的代碼是不是很熟悉?幾乎和flushMessageQueue的后半邏輯代碼是一樣的
也是先檢查responseid
if (message.responseId)
我們請(qǐng)求的參數(shù):

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };

正包含了reponseid

var responseCallback = responseCallbacks[message.responseId]
if (!responseCallback) { return; }
responseCallback(message.responseData)
delete responseCallbacks[message.responseId]

以上代碼就是從responseCallbacks取出相應(yīng)的callback偶宫,然后執(zhí)行完刪除。

這樣孝冒,整個(gè)js調(diào)用native代碼就完成了。

同理可得native調(diào)用js的方式
js中注冊(cè)handler

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

native調(diào)用callhandler

- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
    [_base 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];
}

同樣是包含了self.responseCallbacks本地的回調(diào)函數(shù)隊(duì)列,和message消息
然后執(zhí)行
[self _queueMessage:message];
到j(luò)s的_dispatchMessageFromObjC函數(shù)
此時(shí)也是沒(méi)有reponseid
所以會(huì)去創(chuàng)建回調(diào)函數(shù),并且執(zhí)行js中對(duì)應(yīng)的handler

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

當(dāng)js中調(diào)用回調(diào)函數(shù)的時(shí)候

responseCallback = function(responseData) {
    _doSend({ responseId:callbackResponseId, responseData:responseData })
}

通過(guò)_doSend方法
也包含了reponseidresponsedata
到本地的flushMessageQueue

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

執(zhí)行相應(yīng)的回調(diào)函數(shù)叫挟。

總結(jié)

  • WebViewJavascriptBridge通過(guò)兩種數(shù)據(jù)結(jié)構(gòu)

  • 請(qǐng)求數(shù)據(jù) handlerName,data,callbackid 回調(diào)數(shù)據(jù) responseId , responseData

  • js通過(guò)url重定向,讓本地端主動(dòng)拉取js的請(qǐng)求數(shù)據(jù)進(jìn)行函數(shù)調(diào)用柑肴,然后native再主動(dòng)調(diào)用js代碼霞揉,調(diào)用回調(diào)函數(shù)

  • native通過(guò)主動(dòng)調(diào)用js的代碼去進(jìn)行函數(shù)調(diào)用旬薯,然后js再通過(guò)url重定向晰骑,調(diào)用回調(diào)函數(shù)


關(guān)于UIWebView的內(nèi)存泄漏問(wèn)題,這邊有個(gè)blog,講解的比較好硕舆,可以緩解一下秽荞。

參考鏈接

1.WebViewJavascriptBridge 原理分析
2.WebViewJavascriptBridge-Obj-C和JavaScript互通消息的橋梁
3.UIWebView Secrets - Part1 - Memory Leaks on Xmlhttprequest

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抚官,隨后出現(xiàn)的幾起案子扬跋,更是在濱河造成了極大的恐慌,老刑警劉巖凌节,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钦听,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡倍奢,警方通過(guò)查閱死者的電腦和手機(jī)朴上,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卒煞,“玉大人痪宰,你說(shuō)我怎么就攤上這事∨显#” “怎么了衣撬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)扮饶。 經(jīng)常有香客問(wèn)我具练,道長(zhǎng),這世上最難降的妖魔是什么甜无? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任靠粪,我火速辦了婚禮,結(jié)果婚禮上毫蚓,老公的妹妹穿的比我還像新娘占键。我一直安慰自己,他們只是感情好元潘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布畔乙。 她就那樣靜靜地躺著,像睡著了一般翩概。 火紅的嫁衣襯著肌膚如雪牲距。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天钥庇,我揣著相機(jī)與錄音牍鞠,去河邊找鬼。 笑死评姨,一個(gè)胖子當(dāng)著我的面吹牛难述,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胁后,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼店读!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起攀芯,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤屯断,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后侣诺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體殖演,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年年鸳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剃氧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阻星,死狀恐怖朋鞍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妥箕,我是刑警寧澤滥酥,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站畦幢,受9級(jí)特大地震影響坎吻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宇葱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一瘦真、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧黍瞧,春花似錦诸尽、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至年局,卻和暖如春际看,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矢否。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工仲闽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人僵朗。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓赖欣,卻偏偏與公主長(zhǎng)得像屑彻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畏鼓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容