优秀开源代码解读:JS与iOS N…

来源:互联网 发布:算法工程师学什么专业 编辑:程序博客网 时间:2024/06/18 12:26
本篇为大家介绍一个优秀的开源小项目:WebViewJavascriptBridge
 
它优雅地实现了在使用UIWebView时JS与ios 的Objective-Cnativecode之间的互调,支持消息发送、接收、消息处理器的注册与调用以及设置消息处理的回调。
 
就像项目的名称一样,它是连接UIWebView和Javascript的bridge。在加入这个项目之后,他们之间的交互处理方式变得很友好。
 
在nativecode中跟UIWebView中的js交互的时候,像下面这样:
 
//发送一条消息给UI端并定义回调处理逻辑
 [_bridgesend:@"A string sent from ObjC before Webview has loaded."responseCallback:^(id error, id responseData) {
       if (error){ NSLog(@"Uh oh - I got an error: %@", error); }
      NSLog(@"objc got response! %@ %@", error, responseData);
 }];
 
而在UIWebView中的js跟nativecode交互的时候也变得很简洁,比如在调用处理器的时候,就可以定义回调处理逻辑:
 
//调用名为testObjcCallback的native端处理器,并传递参数,同时设置回调处理逻辑
bridge.callHandler('testObjcCallback', {'foo': 'bar'},function(response) {
log('Got response fromtestObjcCallback', response)
})
 
 一起来看看它的实现吧,它总共就包含了三个文件:
 
WebViewJavascriptBridge.h
WebViewJavascriptBridge.m
WebViewJavascriptBridge.js.txt
 
它们是以如下的模式进行交互的:
 
很明显:WebViewJavascriptBridge.js.txt主要用于衔接UIWebView中的webpage,而WebViewJavascriptBridge.h/m则主要用于与ObjC的nativecode打交道。他们作为一个整体,其实起到了一个“桥梁”的作用,这三个文件封装了他们具体的交互处理方式,只开放出一些对外的涉及到业务处理的API,因此你在需要UIWebView与Nativecode交互的时候,引入该库,则无需考虑太多的交互上的问题。整个的Bridge对你来说都是透明的,你感觉编程的时候,就像是web编程的前端和后端一样清晰。
 
简单地罗列一下它可以实现哪些功能吧:
 
出于表达上的需要,对于UIWebView相关的我就称之为UI端,而objc那端的处理代码称之为Native端。
 
【1】UI端
 
(1)UI端在初始化时支持设置消息的默认处理器(这里的消息指的是从Native端接收到的消息)
 
(2)从UI端向Native端发送消息,并支持对于Native端响应后的回调处理的定义
 
(3)UI端调用Native定义的处理器,并支持Native端响应后的回调处理定义
 
(4)UI端注册处理器(供Native端调用),并支持给Native端响应处理逻辑的定义
 
【2】Native端
 
(1)Native端在初始化时支持设置消息的默认处理器(这里的消息指的是从UI端发送过来的消息)
 
(2)从Native端向UI端发送消息,并支持对于UI端响应后的回调处理逻辑的定义
 
(3)Native端调用UI端定义的处理器,并支持UI端给出响应后在Native端的回调处理逻辑的定义
 
(4)Native端注册处理器(供UI端调用),并支持给UI端响应处理逻辑的定义UI端以及Native端完全是对等的两端,实现也是对等的。一段是消息的发送端,另一段就是接收端。这里为引起混淆,需要解释一下我这里使用的“响应”、“回调”在这个上下文中的定义:
 
(1)响应:接收端给予发送端的应答
 
(2)回调:发送端收到接收端的应答之后在接收端调用的处理逻辑
 
下面来分析一下源码:
 
WebViewJavascriptBridge.js.txt:
 
主要完成了如下工作:
 
(1)创建了一个用于发送消息的iFrame(通过创建一个隐藏的ifrmae,并设置它的URL来发出一个请求,从而触发UIWebView的shouldStartLoadWithRequest回调协议)
 
(2)创建了一个核心对象WebViewJavascriptBridge,并给它定义了几个方法,这些方法大部分是公开的API方法
 
(3)创建了一个事件:WebViewJavascriptBridgeReady,并dispatch(触发)了它。
 
代码解读
 
UI端实现
 
对于(1),相应的代码如下:
 
function_createQueueReadyIframe(doc) {
messagingIframe =doc.createElement_x('iframe')
messagingIframe.style.display = 'none'
doc.documentElement.appendChild(messagingIframe)
}
 

对于(2)中的WebViewJavascriptBridge,其对象拥有如下方法:

window.WebViewJavascriptBridge ={

init: init,

send: send,

registerHandler:registerHandler,

callHandler: callHandler,

_fetchQueue: _fetchQueue,

_handleMessageFromObjC:_handleMessageFromObjC

}

 方法的实现:

 

function init(messageHandler) {

if (WebViewJavascriptBridge._messageHandler) { throw newError('WebViewJavascriptBridge.init called twice') }

WebViewJavascriptBridge._messageHandler = messageHandler

var receivedMessages = receiveMessageQueue

receiveMessageQueue = null

//如果接收队列有消息,则处理

for (var i=0; i

_dispatchMessageFromObjC(receivedMessages[i])

}

}

 

function send(data, responseCallback) {

_doSend({ data:data }, responseCallback)

}

 

 

function registerHandler(handlerName, handler) {

messageHandlers[handlerName] = handler

}

 

 

function callHandler(handlerName, data, responseCallback) {

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

}

 涉及到的两个内部方法:

 

function _doSend(message, responseCallback) {

//如果定义了回调

if (responseCallback) {

//为回调对象产生唯一标识

var callbackId = 'js_cb_'+(uniqueId++)

//并存储到一个集合对象里

responseCallbacks[callbackId] = responseCallback

//新增一个key-value对- 'callbackId':callbackId

message['callbackId'] = callbackId

}

sendMessageQueue.push(JSON.stringify(message))

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' +QUEUE_HAS_MESSAGE

}

 

 

function _dispatchMessageFromObjC(messageJSON) {

setTimeout(function _timeoutDispatchMessageFromObjC() {

var message = JSON.parse(messageJSON)

var messageHandler

 

if (message.responseId) {

//取出回调函数对象并执行

var responseCallback = responseCallbacks[message.responseId]

responseCallback(message.error, message.responseData)

delete responseCallbacks[message.responseId]

} else {

var response

if (message.callbackId) {

var callbackResponseId = message.callbackId

response = {

respondWith: function(responseData) {

_doSend({ responseId:callbackResponseId, responseData:responseData})

},

respondWithError: function(error) {

_doSend({ responseId:callbackResponseId, error:error })

}

}

}

 

var handler = WebViewJavascriptBridge._messageHandler

//如果消息中已包含消息处理器,则使用该处理器;否则使用默认处理器

if (message.handlerName) {

handler = messageHandlers[message.handlerName]

}

 

try {

handler(message.data, response)

} catch(exception) {

console.log("WebViewJavascriptBridge: WARNING: javascript handlerthrew.", message, exception)

}

}

})

}

还有两个js方法是供native端直接调用的方法(它们本身也是为native端服务的):
 
function _fetchQueue(){
var messageQueueString =sendMessageQueue.join(MESSAGE_SEPARATOR)
sendMessageQueue = []
returnmessageQueueString
}
 
 
function_handleMessageFromObjC(messageJSON) {
//如果接收队列对象存在则入队该消息,否则直接处理
if (receiveMessageQueue){
receiveMessageQueue.push(messageJSON)
} else {
_dispatchMessageFromObjC(messageJSON)
}
}
 
最后还有一段代码就是,定义一个事件并触发,同时设置设置上面定义的WebViewJavascriptBridge对象为事件的一个属性:
 
var doc = document
_createQueueReadyIframe(doc)
//创建并实例化一个事件对象
var readyEvent =doc.createEvent('Events')
readyEvent.initEvent('WebViewJavascriptBridgeReady')
readyEvent.bridge =WebViewJavascriptBridge
//触发事件
doc.dispatchEvent(readyEvent)
 
Native端实现
 
其实大致跟上面的类似,只是因为语法不同(所以我上面才说两端是对等的):WebViewJavascriptBridge.h/.m
 
它其实可以看作UIWebView的Controller,实现了UIWebViewDelegate协议:
 
 @interface WebViewJavascriptBridge :NSObject
+(id)bridgeForWebView:(UIWebView*)webViewhandler:(WVJBHandler)handler;
+(id)bridgeForWebView:(UIWebView*)webView webViewDelegate:(id)webViewDelegate handler:(WVJBHandler)handler;
+(void)enableLogging;
-(void)send:(id)message;
- (void)send:(id)messageresponseCallback:(WVJBResponseCallback)responseCallback;
-(void)registerHandler:(NSString*)handlerNamehandler:(WVJBHandler)handler;
-(void)callHandler:(NSString*)handlerName;
-(void)callHandler:(NSString*)handlerName data:(id)data;
-(void)callHandler:(NSString*)handlerName data:(id)dataresponseCallback:(WVJBResponseCallback)responseCallback;
@end
 
方法的实现其实是跟前面类似的,这里我们只看一下UIWebView的一个协议方法:shouldStartLoadWithRequest:
 
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest *)requestnavigationType:(UIWebViewNavigationType)navigationType {
   if (webView != _webView) { return YES; }
   NSURL *url = [request URL];
   if ([[url scheme]isEqualToString:CUSTOM_PROTOCOL_SCHEME]) {
//队列中有数据
       if ([[urlhost] isEqualToString:QUEUE_HAS_MESSAGE]) {
//刷出队列中数据
          [self_flushMessageQueue];
       } else{
         NSLog(@"WebViewJavascriptBridge: WARNING: Received unknownWebViewJavascriptBridge command %@://%@", CUSTOM_PROTOCOL_SCHEME,[url path]);
       }
       returnNO;
   } else if (self.webViewDelegate) {
       return[self.webViewDelegate webView:webViewshouldStartLoadWithRequest:requestnavigationType:navigationType];
   } else {
       returnYES;
   }
}
 
使用示例
 
UI端
 
//给WebViewJavascriptBridgeReady事件注册一个Listener
document.addEventListener('WebViewJavascriptBridgeReady',onBridgeReady, false)
   //事件的响应处理
functiononBridgeReady(event) {
var bridge =event.bridge
var uniqueId = 1
      //日志记录
function log(message, data){
var log =document.getElementByIdx_x('log')
var el =document.createElement_x('div')
el.className ='logLine'
el.innerHTML = uniqueId++ +'. ' + message + (data ? ': ' + JSON.stringify(data) : '')
if (log.children.length) {log.insertBefore(el, log.children[0]) }
else { log.appendChild(el)}
}
      //初始化操作,并定义默认的消息处理逻辑
bridge.init(function(message) {
log('JS got a message',message)
})
      //注册一个名为testJavascriptHandler的处理器,并定义用于响应的处理逻辑
bridge.registerHandler('testJavascriptHandler', function(data,response) {
log('JS handlertestJavascriptHandler was called', data)
response.respondWith({'Javascript Says':'Right back atcha!' })
})
 
      //创建一个发送消息给native端的按钮
var button =document.getElementByIdx_x('buttons').appendChild(document.createElement_x('button'))
button.innerHTML = 'Sendmessage to ObjC'
button.ontouchstart =function(e) {
e.preventDefault()
          //发送消息
bridge.send('Hello from JSbutton')
}
 
document.body.appendChild(document.createElement_x('br'))
 
      //创建一个用于调用native端处理器的按钮
var callbackButton =document.getElementByIdx_x('buttons').appendChild(document.createElement_x('button'))
callbackButton.innerHTML ='Fire testObjcCallback'
callbackButton.ontouchstart= function(e) {
e.preventDefault()
log("Calling handlertestObjcCallback")
         //调用名为testObjcCallback的native端处理器,并传递参数,同时设置回调处理逻辑
bridge.callHandler('testObjcCallback', {'foo': 'bar'},function(response) {
log('Got response fromtestObjcCallback', response)
})
}
}

Native端
 
//实例化一个webview并加入到window中去
   UIWebView* webView = [[UIWebView alloc]initWithFrame:self.window.bounds];
   [self.window addSubview:webView];
   
   //启用日志记录
   [WebViewJavascriptBridge enableLogging];
   
  //实例化WebViewJavascriptBridge并定义native端的默认消息处理器
   _bridge = [WebViewJavascriptBridgebridgeForWebView:webView handler:^(id data, WVJBResponse *response){
      NSLog(@"ObjC received message from JS: %@", data);
      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"ObjC gotmessage from Javascript:" message:data delegate:nilcancelButtonTitle:@"OK" otherButtonTitles:nil];
       [alertshow];
   }];
   
  //注册一个供UI端调用的名为testObjcCallback的处理器,并定义用于响应的处理逻辑
   [_bridge registerHandler:@"testObjcCallback"handler:^(id data, WVJBResponse *response) {
      NSLog(@"testObjcCallback called: %@", data);
       [responserespondWith:@"Response from testObjcCallback"];
   }];
   
   //发送一条消息给UI端并定义回调处理逻辑
   [_bridge send:@"A string sent from ObjC beforeWebview has loaded." responseCallback:^(id error, id responseData){
       if (error){ NSLog(@"Uh oh - I got an error: %@", error); }
      NSLog(@"objc got response! %@ %@", error, responseData);
   }];
   
  //调用一个在UI端定义的名为testJavascriptHandler的处理器,没有定义回调
   [_bridge callHandler:@"testJavascriptHandler"data:[NSDictionary dictionaryWithObject:@"before ready"forKey:@"foo"]];
   
   [self renderButtons:webView];
   [self loadExamplePage:webView];
   
   //单纯发送一条消息给UI端
   [_bridge send:@"A string sent from ObjC afterWebview has loaded."];
 
项目运行截图:
 
 
 
 
来源:http://blog.csdn.net/yanghua_kobe/article/details/8209751 
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 孩子数学不开窍怎么办 二年级 初中孩子语文不开窍怎么办 分到的班级都是差生怎么办 初三了英语差怎么办呀 初三了英语差的很怎么办 五年级英语太差怎么办 听课效率没有自学效率高怎么办 小学生不好好写作业怎么办 带的家教成绩没有提高怎么办 学生出国学校成绩证明怎么办 学生成绩考差了班主任怎么办 高一的学生成绩跟不上怎么办 综合素质评价手册丢了怎么办 小学综合素质评价手册丢了怎么办 人体质不出汗差怎么办 儿子一年级语文成绩太差怎么办 小孩读一年级差几个月怎么办 小孩一年级下学期数学差怎么办 小学五年成绩差怎么办 生完孩子记忆力不好怎么办 生了孩子之后记忆力不好怎么办 孩子记忆力差学习不好怎么办 党课结业证丢了怎么办 对三年级不听话的学生应该怎么办 素质报告单丢了怎么办 社保小红本丢了怎么办 小红本丢了怎么办 孩子小红本丢了怎么办 小学素质报告册丢了怎么办 小学生素质报告册丢了怎么办 高中素质报告册丢了怎么办 小升初素质报告单丢了怎么办? 三供一业移交后社区管理职能怎么办 初中政治总考不好怎么办 孩子上初一学习越来越差怎么办 胸经过整容后变得胸闷怎么办? 网瘾高中不学习怎么办 犯罪人逃到美国怎么办 10岁儿童偏胖怎么办 土地面积与实际面积不符怎么办 军转进省直单位双选失败怎么办