WebViewJavascriptBridge源码分析

最近抽时间看了一遍WebViewJavascriptBridge这个开源框架,把看到的内容记录下来
源码地址:https://github.com/marcuswestin/WebViewJavascriptBridge


1、对外接口

初始化OC
初始化JS

**[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview handler:(WVJBHandler)handler]**
** **
**[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview webViewDelegate:(UIWebViewDelegate*)webViewDelegate handler:(WVJBHandler)handler]**
 

**document.addEventListener('WebViewJavascriptBridgeReady', function onBridgeReady(event) { ... }, false)**
** **
**bridge.init(function messageHandler(data, response) { ... })**

OC发送消息to JS
JS发送消息to OC

**[bridge send:(id)data]**
**[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]**
 

**bridge.send("Hi there!")**
**bridge.send({ Foo:"Bar" })**
**bridge.send(data, function responseCallback(responseData) { ... })**

OC注册事件(先)
JS调用事件(后)

**[bridge registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler]**
 

WebViewJavascriptBridge.callHandler("handlerName")

OC调用事件(后)
JS注册事件(先)

**[bridge callHandler:(NSString*)handlerName data:(id)data]**
**[bridge callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)callback]**
 

**bridge.registerHandler("handlerName", function(responseData) { ... })**

三类API接口用于OC与JS之间交互:初始化接口;发送消息接口,并且可以添加发送消息完成的回调函数;事件注册和调用接口,需要先在一端注册事件,另一端可以根据事件名称调用函数

除了上述提到的外部方法:还有两个方法十分重要,**JS部分最重要的内部方法:**_handleMessageFromObjC;OC部分重要的内部方法:**flushMessageQueue******
2、类结构图


WebViewJavascriptBridge目前既支持原有的UIWebView,也支持iOS8+之后新的WKWebView,使用时可以二选其一;
WebViewjavascriptBridgeBase是通用类,用于处理从Native到JS的消息注入,消息队列处理和分发,JSON数据的序列化和反序列化,LOG输出;

3、源码分析

![](http://upload-images.jianshu.io/upload_images/1708245-fccb0efdbf86069e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

3.1 消息发送JS-》Native


**[bridge send:(id)data]**

**[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]**

这两个函数最后都是调用_doSend({ 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}

首先生成callbackId,由不断加1的唯一需要和时间戳构成,如果有responseCallback函数,使用callbackId作为索引,存入responseCallbacks对象,等到从OC侧返回的信息中对应的callbackId与当前responseCallbacks中callbackId相同时,调用回调函数responseCallback;sendMessageQueue是个消息数组,每次的新消息放入其中,messagingIframe是iframe对象,当设置src产生一次请求,在OC端的

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 会拦截请求内容

代码:

- (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 ([_baseisCorrectProcotocolScheme:url]) {        if ([_baseisCorrectHost:url]) {            NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];            [_base flushMessageQueue:messageQueueString];        } else {            [_base logUnkownMessage:url];        }        return NO;    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {        return [strongDelegate webView:webView shouldStartLoadWithRequest:requestnavigationType:navigationType];    } else {        return YES;    }
}

重点部分:执行注入_evaluateJavascript,
OC

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

这个函数从JS的sendMessageQueue消息队列获取内容返回,这个sendMessageQueue是在之前的_doSend函数中传入的消息内容,也就是NSString*messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];这句代码获得从JS侧拿到的数据内容,然后调用[_baseflushMessageQueue:messageQueueString];对消息分发处理

- (void)flushMessageQueue:(NSString *)messageQueueString{    id messages = [self_deserializeMessageJSON:messageQueueString];    if (![messages isKindOfClass:[NSArray class]]) {        NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messagesclass], messages);        return;    }    for (WVJBMessage* messagein messages) {        if (![message isKindOfClass:[WVJBMessage class]]) {            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messageclass], message);            continue;        }        [self _log:@"RCVD" json:message];               NSString* responseId = message[@"responseId"];        if (responseId) {            WVJBResponseCallback responseCallback =_responseCallbacks[responseId];            responseCallback(message[@"responseData"]);            [self.responseCallbacksremoveObjectForKey:responseId];        } else {            WVJBResponseCallback responseCallback =NULL;            NSString* callbackId = message[@"callbackId"];            if (callbackId) {                responseCallback = ^(id responseData) {                    if (responseData == nil) {                        responseData = [NSNullnull];                    }                                       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];            }                       handler(message[@"data"], responseCallback);        }    }
}

这个是整个框架中OC侧重要的函数,但是目前首先分析消息发送JS-》Native涉及到的部分内容,返回的消息包含callbackId,数据拼接后调用[self_queueMessage:msg];发送回JS侧的数据改为responseId为关键字key,具体如下:

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

self.startupMessageQueue只有首次启动时有效,之后为nil,所以都是走[self_dispatchMessage:message];

- (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 = [NSStringstringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];    if ([[NSThreadcurrentThread] isMainThread]) {        [self _evaluateJavascript:javascriptCommand];    } else {        dispatch_sync(dispatch_get_main_queue(), ^{            [self _evaluateJavascript:javascriptCommand];        });    }
}

此函数对message特殊字符进行转义处理,然后执行JS注入语句,WebViewJavascriptBridge._handleMessageFromObjC执行到JS侧

这个是整个框架中JS侧重要的函数,用于处理从OC侧返回的消息

function _dispatchMessageFromObjC(messageJSON) {        setTimeout(function _timeoutDispatchMessageFromObjC() {            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({ 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)                    }                }            }        })    }

执行JS侧本地回调函数

3.2 消息发送 OC--》JS

**bridge.send("Hi there!")**
**bridge.send({ Foo:"Bar" })**

**bridge.send(data, function responseCallback(responseData) { ... })**
****

调用
[_base sendData:dataresponseCallback:responseCallback handlerName:nil];

执行 _queueMessage

3.3 OC注册事件和JS调用

OC侧注册

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handlercopy];
}

JS调用

function callHandler(handlerName, data, responseCallback) {
     _doSend({ handlerName:handlerName, data:data }, responseCallback)
}

加入handlerName和data数据传给OC侧,JS侧记录responseCallback,最后也会走到- (void)flushMessageQueue:(NSString )messageQueueString函数中,由于既没有callbackId也没有responseId,所以只处理handlerName及相关数据,最后走到 - (void)flushMessageQueue:(NSString)messageQueueString解析,OC侧执行之前注册的handler并传入data数据

3.4 JS注册事件和OC调用


JS注册

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

本地记录

OC调用

- (void)callHandler:(NSString *)handlerName data:(id)data {    [self callHandler:handlerName data:dataresponseCallback:nil];
}
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {    [_base sendData:data responseCallback:responseCallbackhandlerName:handlerName];
}

handlerName和data 在

 - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName

中处理

**4 、总结**
****
1、与其它框架相比,此框架没有采用url拦截解析参数方式,而是多次JS注入参数获取,API接口暴露的操作在底层需要多次OC与JS之间交互完成
2、WKwebview部分没有使用新的delgate方法,而是沿用iframe方式,个人觉得采用新接口可能效率更高
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容