近期抽时间看了一遍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. 【转】从源码来分析ListView

    原文:http://yanmingming.sinaapp.com/?p=1251 原文其实不叫这个名字,本文对于原文有一定的修改,觉得这个名字比较适合本篇. 一.ListView 的工作原理 Ada ...

  2. information_schema系列七

    information_schema系列七   这个系列的文章主要是为了能够让自己了解MySQL5.7的一些系统表,统一做一下备注和使用,也希望分享出来让大家能够有一点点的受益. 第七篇主要看一下一下 ...

  3. C# 异步编程Task整理(二)异常捕捉

    一.在任务并行库中,如果对任务运行Wait.WaitAny.WaitAll等方法,或者求Result属性,都能捕获到AggregateException异常. 可以将AggregateExceptio ...

  4. 【Java并发】JUC—ReentrantReadWriteLock有坑,小心读锁!

    好长一段时间前,某些场景需要JUC的读写锁,但在某个时刻内读写线程都报超时预警(长时间无响应),看起来像是锁竞争过程中出现死锁(我猜).经过排查项目并没有能造成死锁的可疑之处,因为业务代码并不复杂(仅 ...

  5. 虚拟机内存复用技术的比较(XEN系统)

    技术途径 业界就该问题定义为虚拟机内存复用(复用干嘛? 当然是为了跑更多的虚拟机呀!) :memory overcommit.围绕次问题主要有4种技术手段,下面简要介绍和分析: 1 气泡驱动(ball ...

  6. 如何打开google,facebok等网站

    用记事本打开 C:\WINDOWS\System32\drivers\etc\hosts 文件,粘贴如下蓝色内容到文件里,然后输入 例如 https://www.google.com   https: ...

  7. 《UNIX环境高级编程》笔记--环境变量

    ISO C定义了一个函数getenv,可以用其取环境变量值. #include <stdlib.h> char* getenv(const char* name); //返回与name关联 ...

  8. 第二章 TypeScript 开发环境搭建

    Mac OS X 下 TypeScript 开发环境搭建 一.集成开发环境 WebStrom VSCode 二.安装 TypeScript Homebrew(macOS 缺失的软件包管理器) ruby ...

  9. [leetcode]Combinations @ Python

    原题地址:https://oj.leetcode.com/problems/combinations/ 题意:组合求解问题. 解题思路:这种求组合的问题,需要使用dfs来解决. 代码: class S ...

  10. Hadoop视频教程汇总

    一 慕课网 1.Hadoop大数据平台架构与实践--基础篇(已学习) 链接:https://www.imooc.com/learn/391 2.Hadoop进阶(已学习) 链接:https://www ...