首先请参看一篇文章,作者写的很明白,请参看原地址 http://blog.163.com/m_note/blog/static/208197045201293015844274/

其实,oc和js的交互涉及的就是UIWebview的2个最主要的方法,stringByEvaluatingJavaScriptFromString: 和- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType 。第一个方法的主要功能是注入和执行javascript,具体是注入还是执行,要看你参照中的string的格式,被用来从oc调用js。第二个方法的主要是根据请求的url进行分析,做出响应的处理,被用来从js调用oc。


下面我们分析一下WebViewJavascriptBridge工程,请到github上下载这个工程,注意阅读其中的readme文档!

关于这个工程,这里的bridge是以oc的角度命名方法的,比如这里的send,就是指利用oc代码调用javascript代码,而receive就是指javascript代码调用oc代码。这里的方法命名,log输出都是用的这个规则。知道了这个规则,方便理解代码和log。

ios的demo截图如下,共有4个按钮,上边2个是html中的按钮,下面2个是xib中的按钮,上面的按钮是为了演示js调用oc,下面的是为了演示oc调用js。

第一个按钮的演示的是:js发送data到oc,并调用oc中的默认处理方法处理data,处理完成后,oc回调一个js方法。

第二个按钮演示的是:js发送data到oc,并调用指定名称的oc方法去处理data,处理完成后,oc回调一个js方法。

第三个,和第四个的作用和上边的差不多,只不过是从oc向js发送。

我们看一下“点击左上按钮”这个操作的流程,了解一下实现细节。

a 首先是js方面的处理

调用按钮的onclick方法

  1. button.onclick = function(e) {
  2. e.preventDefault()
  3. var data = 'Hello from JS button'
  4. log('JS sending message', data)
  5. bridge.send(data, function(responseData) {
  6. log('JS got response', responseData)
  7. })
  8. }

send 方法如下

  1. function send(data, responseCallback) {
  2. _doSend({ data:data }, responseCallback)
  3. }

_doSend方法如下

  1. function _doSend(message, responseCallback) {
  2. if (responseCallback) {
  3. var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
  4. responseCallbacks[callbackId] = responseCallback
  5. message['callbackId'] = callbackId
  6. }
  7. sendMessageQueue.push(message)
  8. messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
  9. }

这段代码需要分析一下,

responseCallbacks是一个map,负责把函数对象responseCallback和callbackId向关联。

message在传入的时候已经包含了data条目,这里又为它添加了callbackId条目,这样一条信息发送数据和回掉方法都被记录到了message中。

之后系统调用了sendMessageQueue.push(message)向数组加入message。

最后,系统更改iframe元素的src属性,这会导致UIWebView调用代理方法- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType。这样就可以调用相应的oc方法了。


b oc的任务开始了

  1. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
  2. if (webView != _webView) { return YES; }
  3. NSURL *url = [request URL];
  4. NSLog(@"shouldStartLoadWithRequest url is %@",url);
  5. __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
  6. if ([[url scheme] isEqualToString:kCustomProtocolScheme]) {
  7. if ([[url host] isEqualToString:kQueueHasMessage]) {
  8. [self _flushMessageQueue];
  9. } else {
  10. NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);
  11. }
    //注意这里的NO,这样就不会导致WebView加载数据了!因为我们发送的路径根本不是一个有内容的路径
  12. return NO;
  13. } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
  14. return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
  15. } else {
  16. return YES;
  17. }
  18. }

系统首先检查是不是自己发出的请求,检查方法是查url的scheme,这里是 wvjbscheme  字符串,之后又检查了主机名是不是 __WVJB_QUEUE_MESSAGE__ 字符串。其实这里就属于个人定制部分了,因为这里仅仅是个demo,你可以添加更多的 hostname,去定义更多的处理方法。


之后系统调用_flushMessageQueue方法

这里的WVJBResponseCallback函数代表oc调用js后的回调函数。

系统首先通过执行js,获得到了当前在js中保存的sendMessageQueue,之后对数组中的每条message分别处理。

  1. //主要作用就是取得在js中保存messageQueue,并做出操作
  2. - (void)_flushMessageQueue {
  3. NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];
  4.  
  5. id messages = [self _deserializeMessageJSON:messageQueueString];
  6. if (![messages isKindOfClass:[NSArray class]]) {
  7. NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);
  8. return;
  9. }
  10. for (WVJBMessage* message in messages) {
  11. if (![message isKindOfClass:[WVJBMessage class]]) {
  12. NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
  13. continue;
  14. }
  15. [self _log:@"RCVD" json:message];
  16.  
  17. //检查message字典中是否存在responseId条目,如果存在,那么这条message是js收到oc的调用后,返回的响应消息,这条消息会调用指定的oc回调函数,处理比较简单;如果不存在,那么这是一条js发出的消息,处理比较复杂。
  18. NSString* responseId = message[@"responseId"];
  19. if (responseId) {
  20. WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
  21. responseCallback(message[@"responseData"]);
  22. [_responseCallbacks removeObjectForKey:responseId];
  23. } else {
  24. WVJBResponseCallback responseCallback = NULL;
  25. //callbackId 代表了js的回掉函数,因此,如果存在这个条目,那么oc应该负责发送一条message,去调用js的这个回掉函数,如果不存在那么就不必操作了。
  26. NSString* callbackId = message[@"callbackId"];
  27. if (callbackId) {
  28. responseCallback = ^(id responseData) {
  29. WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
  30. [self _queueMessage:msg];
  31. };
  32. } else {
  33. responseCallback = ^(id ignoreResponseData) {
  34. // Do nothing
  35. };
  36. }
  37.  
  38. //handlerName 代表js要条用的oc的方法名称,如果存在,那么数据和回掉方法都要由oc中的handler处理;如果不存在,那么系统用创建bridge时传入的handle处理。
  39. WVJBHandler handler;
  40. if (message[@"handlerName"]) {
  41. handler = _messageHandlers[message[@"handlerName"]];
  42. //没有注册相应的handler
  43. if (!handler) {
  44. NSLog(@"WVJB Warning: No handler for %@", message[@"handlerName"]);
  45. return responseCallback(@{});
  46. }
  47. } else {
  48. handler = _messageHandler;
  49. }
  50.  
  51. @try {
  52. id data = message[@"data"];
  53. handler(data, responseCallback);
  54. }
  55. @catch (NSException *exception) {
  56. NSLog(@"WebViewJavascriptBridge: WARNING: objc handler threw. %@ %@", message, exception);
  57. }
  58. }
  59. }
  60. }

系统每处理一条message,就打印出类似于下边的log

  1. WVJB RCVD: {"data":"Hello from JS button","callbackId":"cb_2_1394094418895"}

在js中其实又2个数组保存message,一个是sendMessageQueue代表js向oc发出的消息,另一个是receiveMessageQueue代表oc向js发出的消息。

在oc中只有一个_startupMessageQueue保存message,这些message都是oc向js发送的消息,而js向oc发送的消息,都是通过调用- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType方法进行的,这样就不需要把消息再保存一遍了。

为什么不需要再保存呢?这里就涉及到另一个问题,为什么需要messageQueue呢?我对messageQueue的理解是,不能够或者不方便立即对message做出处理。例如 oc中的这个

_startupMessageQueue,我们看看程序启动时的log

  1. -- ::14.288 ExampleApp-iOS[:a0b] shouldStartLoadWithRequest url is about:blank
  2. -- ::14.289 ExampleApp-iOS[:a0b] webViewDidStartLoad
  3. -- ::14.319 ExampleApp-iOS[:a0b] webViewDidFinishLoad.......
  4. -- ::14.321 ExampleApp-iOS[:a0b] go on
  5. -- ::14.327 ExampleApp-iOS[:a0b] shouldStartLoadWithRequest url is about:blank
  6. -- ::14.329 ExampleApp-iOS[:a0b] webViewDidStartLoad
  7. -- ::14.331 ExampleApp-iOS[:a0b] webViewDidFinishLoad.......
  8. -- ::14.332 ExampleApp-iOS[:a0b] go on
  9. -- ::14.332 ExampleApp-iOS[:a0b] bigger than one!!!!------------
  10. -- ::14.333 ExampleApp-iOS[:a0b] _dispatchMessage : {
  11. callbackId = "objc_cb_1";
  12. data = "A string sent from ObjC before Webview has loaded.";
  13. }
  14. -- ::14.333 ExampleApp-iOS[:a0b] WVJB SEND: {"data":"A string sent from ObjC before Webview has loaded.","callbackId":"objc_cb_1"}
  15. -- ::14.334 ExampleApp-iOS[:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{\"data\":\"A string sent from ObjC before Webview has loaded.\",\"callbackId\":\"objc_cb_1\"}');
  16. -- ::14.335 ExampleApp-iOS[:a0b] _dispatchMessage : {
  17. data = {
  18. foo = "before ready";
  19. };
  20. handlerName = testJavascriptHandler;
  21. }
  22. -- ::14.335 ExampleApp-iOS[:a0b] WVJB SEND: {"data":{"foo":"before ready"},"handlerName":"testJavascriptHandler"}
  23. -- ::14.336 ExampleApp-iOS[:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{\"data\":{\"foo\":\"before ready\"},\"handlerName\":\"testJavascriptHandler\"}');
  24. -- ::14.336 ExampleApp-iOS[:a0b] _dispatchMessage : {
  25. data = "A string sent from ObjC after Webview has loaded.";
  26. }
  27. -- ::14.337 ExampleApp-iOS[:a0b] WVJB SEND: {"data":"A string sent from ObjC after Webview has loaded."}
  28. -- ::14.337 ExampleApp-iOS[:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{\"data\":\"A string sent from ObjC after Webview has loaded.\"}');

结合log,我们再看看启动代码

  1. [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
  2. NSLog(@"testObjcCallback called: %@", data);
  3. responseCallback(@"Response from testObjcCallback");
  4. }];
  5.  
  6. [_bridge send:@"A string sent from ObjC before Webview has loaded." responseCallback:^(id responseData) {
  7. NSLog(@"objc got response! %@", responseData);
  8. }];
  9.  
  10. [_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
  11.  
  12. [self renderButtons:webView];
  13. //webview 载入内容
  14. [self loadExamplePage:webView];
  15.  
  16. [_bridge send:@"A string sent from ObjC after Webview has loaded."];

注意到了吗,在webView载入代码之前,我们就可以发送消息了!但这时候webview没载入内容,当然不能响应我们的message了!所以我们要先把它们缓存起来,等待webViewDidFinishLoad后才能执行所需要的代码。

另外从log中看出,webViewDidFinishLoad是可能执行多次的,(原因是什么呢?) 为了保证messageQueue不被多次执行,需要在执行后把queue设置为nil

  1. - (void)webViewDidFinishLoad:(UIWebView *)webView {
  2. NSLog(@"webViewDidFinishLoad.......");
  3. if (webView != _webView) { return; }
  4. _numRequestsLoading--;
  5.  
  6. if (_numRequestsLoading == && ![[webView stringByEvaluatingJavaScriptFromString:@"typeof WebViewJavascriptBridge == 'object'"] isEqualToString:@"true"]) {
  7. NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
  8. NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
  9. NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
  10. [webView stringByEvaluatingJavaScriptFromString:js];
  11. }
  12.  
  13. if (_startupMessageQueue) {
  14. if([_startupMessageQueue count]>)
  15. NSLog(@"bigger than one!!!!------------%d",[_startupMessageQueue count]);
  16. for (id queuedMessage in _startupMessageQueue) {
  17. [self _dispatchMessage:queuedMessage];
  18. }
  19. _startupMessageQueue = nil;
  20. }
  21.  
  22. __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
  23. if (strongDelegate && [strongDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
  24. [strongDelegate webViewDidFinishLoad:webView];
  25. }
  26. }

iOS 使用UIWebView把oc代码和javascript相关联的更多相关文章

  1. iOS中UIWebView执行JS代码(UIWebView)

    iOS中UIWebView执行JS代码(UIWebView) 有时候iOS开发过程中使用 UIWebView 经常需要加载网页,但是网页中有很多明显的标记让人一眼就能看出来是加载的网页,而我们又不想被 ...

  2. iOS中UIWebView与其中网页的javascript的交互

    首发:个人博客,更新&纠错&回复 1.本地语言调js的方式与android中的方式类似,也是向WebView控件发送要调用的js语句 2. 但js调本地语言,则不是像android那样 ...

  3. iOS下JS与OC互相调用(五)--UIWebView + WebViewJavascriptBridge

    WebViewJavascriptBridge是一个有点年代的JS与OC交互的库,使用该库的著名应用还挺多的,目前这个库有7000+star.我去翻看了它的第一版本已经是4年前了,在版本V4.1.4以 ...

  4. iOS(UIWebView 和WKWebView)OC与JS交互 之二

    在iOS应用的开发过程中,我们经常会使用到WebView,当我们对WebView进行操作的时候,有时会需要进行源生的操作.那么我记下来就与大家分享一下OC与JS交互. 首先先说第一种方法,并没有牵扯O ...

  5. Unity3D研究院之IOS全自动编辑framework、plist、oc代码

    Unity打IOS时会先生成一个Xcode工程,如果你需要增加一些第三方的framework那么需要手动一条一条的添加,这太烦了..而且可能你还需要修改Plist文件,甚至还可能要修改unity自动生 ...

  6. iOS中JS 与OC的交互(JavaScriptCore.framework)

    iOS中实现js与oc的交互,目前网上也有不少流行的开源解决方案: 如:react native 当然一些轻量级的任务使用系统提供的UIWebView 以及JavaScriptCore.framewo ...

  7. iOS - UI - UIWebView

    1.UIWebView UIWebView 是 苹果提供的用来展示网页的UI控件.它也是最占内存的控件. iOS8.0 webkit框架. WKWebView,相比UIWebView,节省了1/3~1 ...

  8. iOS下JS与OC互相调用(四)--JavaScriptCore

    前面讲完拦截URL的方式实现JS与OC互相调用,终于到JavaScriptCore了.它是从iOS7开始加入的,用 Objective-C 把 WebKit 的 JavaScript 引擎封装了一下, ...

  9. ios--网页js调用oc代码+传递参数+避免中文参数乱码的解决方案(实例)

    此解决方案原理: 1.在ViewController.h中声明方法和成员变量,以及webView的委托: // //  ViewController.h //  JS_IOS_01 // //  Cr ...

随机推荐

  1. 关于obj和基本类通过函数参数传进去执行是否改变原来的值

    var obj = { p1 : 1, p2 : 2 }; (function(_/* 这个东东是地址的应用哦 */){ _.p1 = 3, _.p2 = 4 })(obj) var i = 2; ( ...

  2. windows Server2008R2 每隔一段时间自动关机解决办法

    情况描述: “我的电脑-->右键属性”中显示“已激活”,而“管理工具”中显示未激活.系统中有进程wlms.exe. 网上找了下解决方式: 1.提权工具:PSTOOLS(下载地址:http://m ...

  3. 什么时候用Vector, 什么时候改用ArrayList?

    转自:http://www.cnblogs.com/langtianya/archive/2012/08/28/2659787.html 书得到的信息好像是Vector是从java1开始就有了,Arr ...

  4. 【CodeForces 621A】Wet Shark and Odd and Even

    题 Today, Wet Shark is given n integers. Using any of these integers no more than once, Wet Shark wan ...

  5. POJ2187 Beauty Contest

    Description Bessie, Farmer John's prize cow, has just won first place in a bovine beauty contest, ea ...

  6. 友盟iOS推送配置(从真机调试到推送)

    下面我来讲解一下友盟iOS的推送配置,其实友盟只是一个示例,换做其余的第三方推送服务也会适用,只是第三方的后面服务变了而已. iOS推送(包括真机调试)所需要的步骤和文件如下: 备注:这里我将省略掉一 ...

  7. UVa 11988 Broken Keyboard (a.k.a. Beiju Text)

    题目复制太麻烦了,甩个链接 http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18693 直接模拟光标操作时间复杂度较高,所以用链 ...

  8. Linux的学习路线图

    一.学习Linux的基本要求1. 掌握至少50个以上的常用命令. 2. 熟悉Gnome/KDE等X-windows桌面环境操作 . 3. 掌握.tgz..rpm等软件包的常用安装方法 4. 学习添加外 ...

  9. SmartImageView&常见的开源代码

    1)说明: 该控件实现图片的显示----网络路径也可以显示出来---加载完成之后 就可以 缓存到内存里面!

  10. Spring学习8-用MyEclipse搭建SSH框架 Struts Spring Hibernate

    1.new一个web project. 2.右键项目,为项目添加Struts支持. 点击Finish.src目录下多了struts.xml配置文件. 3.使用MyEclipse DataBase Ex ...