近期抽时间看了一遍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、源代码分析

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" align="middle" alt="" />

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为keywordkey,详细例如以下:
- (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方式,个人认为採用新接口可能效率更高

WebViewJavascriptBridge源代码分析的更多相关文章

  1. android-plugmgr源代码分析

    android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...

  2. Twitter Storm源代码分析之ZooKeeper中的目录结构

    徐明明博客:Twitter Storm源代码分析之ZooKeeper中的目录结构 我们知道Twitter Storm的所有的状态信息都是保存在Zookeeper里面,nimbus通过在zookeepe ...

  3. 转:SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

  4. 转:RTMPDump源代码分析

    0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...

  5. 转:ffdshow 源代码分析

    ffdshow神奇的功能:视频播放时显示运动矢量和QP FFDShow可以称得上是全能的解码.编码器.最初FFDShow只是mpeg视频解码器,不过现在他能做到的远不止于此.它能够解码的视频格式已经远 ...

  6. UiAutomator源代码分析之UiAutomatorBridge框架

    上一篇文章<UIAutomator源代码分析之启动和执行>我们描写叙述了uitautomator从命令行执行到载入測试用例执行測试的整个流程.过程中我们也描写叙述了UiAutomatorB ...

  7. MyBatis架构设计及源代码分析系列(一):MyBatis架构

    如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...

  8. hostapd源代码分析(三):管理帧的收发和处理

    hostapd源代码分析(三):管理帧的收发和处理 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004379 这篇文章我来讲解一下h ...

  9. hostapd源代码分析(二):hostapd的工作机制

    [转]hostapd源代码分析(二):hostapd的工作机制 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004433 在我的上一 ...

随机推荐

  1. 用开源项目SwitchButton实现各种风格的switch

    今天介绍的开源项目是否的优秀,又是国人的作品.之前我接触过很多很多的自定义switch,有些动画僵硬,有些不能自定义switch的宽度,有些只能定义宽度不能设置滑块的宽高.但,这个项目提供了各种定制的 ...

  2. [转]php 操作数组 (合并,拆分,追加,查找,删除等)

    From : http://justcoding.iteye.com/blog/1181962 1. 合并数组 array_merge()函数将数组合并到一起,返回一个联合的数组.所得到的数组以第一个 ...

  3. [转]通过查看mysql 配置参数、状态来优化你的mysql

    From : http://wangwei007.blog.51cto.com/68019/967278 mysql的监控方法大致分为两类: 1.连接到mysql数据库内部,使用show status ...

  4. scala编程第15章

    package myscala15import myscala.Element.elemimport myscala.Element sealed abstract class Expr case c ...

  5. C++ 内置宏定义 与 预编译指令

    内置宏和预编译指令, 在代码调试.单元测试.跨平台代码中经常会用到.这里记录一下. 1. 内置宏 (文件名,当前行号,当前日期,当前时间,当前执行方法名) __FILE____LINE____DATE ...

  6. go语言之进阶篇runtime包中 Gosched Goexit GOMAXPROCS的使用

    一.runtime包 1.Gosched的使用 runtime.Gosched() 用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢 ...

  7. go语言之进阶篇方法的重写

    1.方法的重写 示例: //Person类型,实现了一个方法 func (tmp *Person) PrintInfo() { fmt.Printf("name=%s, sex=%c, ag ...

  8. Http协议中Get和Post的浅谈

    起名困难户,每次写文章最愁的就是不知道该如何起个稍具内涵的名字,如果这篇文章我只是写写Get和Post的区别,我可以起个名字“Get和Post的那点事”,如果打算阐述一下Http协议原理性内容,那该叫 ...

  9. Git SVN 版本控制 简介 总结 MD

    Git 使用准备 主流的 Git 托管网站 GitLab,主流网站,私有仓库也完全免费,功能更强大,页面精美,操作方便 GitHub,最著名的免费Git托管网站,缺点是免费的不支持私有项目 OSChi ...

  10. android 框架层 常用类介绍

    名称 功能描述 示意图 activitymanager 管理应用程序的周期并提供常用的回退功能 window manager 窗口管理者 content provider 用于访问另一个的数据,或者共 ...