zoukankan      html  css  js  c++  java
  • iOS 使用UIWebView把oc代码和javascript相关联

    首先请参看一篇文章,作者写的很明白,请参看原地址 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方法

    button.onclick = function(e) {
                e.preventDefault()
                var data = 'Hello from JS button'
                log('JS sending message', data)
                bridge.send(data, function(responseData) {
                    log('JS got response', responseData)
                })
            }

    send 方法如下

    function send(data, responseCallback) {
            _doSend({ data:data }, responseCallback)
        }

    _doSend方法如下

    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
        }

    这段代码需要分析一下,

    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的任务开始了

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

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


    之后系统调用_flushMessageQueue方法

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

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

    //主要作用就是取得在js中保存messageQueue,并做出操作
    - (void)_flushMessageQueue {
        NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];
        
        id messages = [self _deserializeMessageJSON:messageQueueString];
        if (![messages isKindOfClass:[NSArray class]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);
            return;
        }
        for (WVJBMessage* message in messages) {
            if (![message isKindOfClass:[WVJBMessage class]]) {
                NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
                continue;
            }
            [self _log:@"RCVD" json:message];
    
            //检查message字典中是否存在responseId条目,如果存在,那么这条message是js收到oc的调用后,返回的响应消息,这条消息会调用指定的oc回调函数,处理比较简单;如果不存在,那么这是一条js发出的消息,处理比较复杂。
            NSString* responseId = message[@"responseId"];
            if (responseId) {
                WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
                responseCallback(message[@"responseData"]);
                [_responseCallbacks removeObjectForKey:responseId];
            } else {
                WVJBResponseCallback responseCallback = NULL;
                //callbackId 代表了js的回掉函数,因此,如果存在这个条目,那么oc应该负责发送一条message,去调用js的这个回掉函数,如果不存在那么就不必操作了。
                NSString* callbackId = message[@"callbackId"];
                if (callbackId) {
                    responseCallback = ^(id responseData) {
                        WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                        [self _queueMessage:msg];
                    };
                } else {
                    responseCallback = ^(id ignoreResponseData) {
                        // Do nothing
                    };
                }
                
                //handlerName 代表js要条用的oc的方法名称,如果存在,那么数据和回掉方法都要由oc中的handler处理;如果不存在,那么系统用创建bridge时传入的handle处理。
                WVJBHandler handler;
                if (message[@"handlerName"]) {
                    handler = _messageHandlers[message[@"handlerName"]];
                    //没有注册相应的handler
                    if (!handler) {
                        NSLog(@"WVJB Warning: No handler for %@", message[@"handlerName"]);
                        return responseCallback(@{});
                    }
                } else {
                    handler = _messageHandler;
                }
                
                @try {
                    id data = message[@"data"];
                    handler(data, responseCallback);
                }
                @catch (NSException *exception) {
                    NSLog(@"WebViewJavascriptBridge: WARNING: objc handler threw. %@ %@", message, exception);
                }
            }
        }
    }

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

    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

    2014-03-07 11:27:14.288 ExampleApp-iOS[26442:a0b] shouldStartLoadWithRequest url is about:blank
    2014-03-07 11:27:14.289 ExampleApp-iOS[26442:a0b] webViewDidStartLoad
    2014-03-07 11:27:14.319 ExampleApp-iOS[26442:a0b] webViewDidFinishLoad.......
    2014-03-07 11:27:14.321 ExampleApp-iOS[26442:a0b] go on
    2014-03-07 11:27:14.327 ExampleApp-iOS[26442:a0b] shouldStartLoadWithRequest url is about:blank
    2014-03-07 11:27:14.329 ExampleApp-iOS[26442:a0b] webViewDidStartLoad
    2014-03-07 11:27:14.331 ExampleApp-iOS[26442:a0b] webViewDidFinishLoad.......
    2014-03-07 11:27:14.332 ExampleApp-iOS[26442:a0b] go on
    2014-03-07 11:27:14.332 ExampleApp-iOS[26442:a0b] bigger than one!!!!------------3
    2014-03-07 11:27:14.333 ExampleApp-iOS[26442:a0b] _dispatchMessage : {
        callbackId = "objc_cb_1";
        data = "A string sent from ObjC before Webview has loaded.";
    }
    2014-03-07 11:27:14.333 ExampleApp-iOS[26442:a0b] WVJB SEND: {"data":"A string sent from ObjC before Webview has loaded.","callbackId":"objc_cb_1"}
    2014-03-07 11:27:14.334 ExampleApp-iOS[26442:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{"data":"A string sent from ObjC before Webview has loaded.","callbackId":"objc_cb_1"}');
    2014-03-07 11:27:14.335 ExampleApp-iOS[26442:a0b] _dispatchMessage : {
        data =     {
            foo = "before ready";
        };
        handlerName = testJavascriptHandler;
    }
    2014-03-07 11:27:14.335 ExampleApp-iOS[26442:a0b] WVJB SEND: {"data":{"foo":"before ready"},"handlerName":"testJavascriptHandler"}
    2014-03-07 11:27:14.336 ExampleApp-iOS[26442:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{"data":{"foo":"before ready"},"handlerName":"testJavascriptHandler"}');
    2014-03-07 11:27:14.336 ExampleApp-iOS[26442:a0b] _dispatchMessage : {
        data = "A string sent from ObjC after Webview has loaded.";
    }
    2014-03-07 11:27:14.337 ExampleApp-iOS[26442:a0b] WVJB SEND: {"data":"A string sent from ObjC after Webview has loaded."}
    2014-03-07 11:27:14.337 ExampleApp-iOS[26442:a0b] javascript command is WebViewJavascriptBridge._handleMessageFromObjC('{"data":"A string sent from ObjC after Webview has loaded."}');

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

        [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
            NSLog(@"testObjcCallback called: %@", data);
            responseCallback(@"Response from testObjcCallback");
        }];
        
        [_bridge send:@"A string sent from ObjC before Webview has loaded." responseCallback:^(id responseData) {
            NSLog(@"objc got response! %@", responseData);
        }];
        
        [_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
        
        [self renderButtons:webView];
        //webview 载入内容
        [self loadExamplePage:webView];
        
        [_bridge send:@"A string sent from ObjC after Webview has loaded."];

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

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

    - (void)webViewDidFinishLoad:(UIWebView *)webView {
        NSLog(@"webViewDidFinishLoad.......");
        if (webView != _webView) { return; }
        _numRequestsLoading--;
        
        if (_numRequestsLoading == 0 && ![[webView stringByEvaluatingJavaScriptFromString:@"typeof WebViewJavascriptBridge == 'object'"] isEqualToString:@"true"]) {
            NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
            NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
            NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
            [webView stringByEvaluatingJavaScriptFromString:js];
        }
        
        if (_startupMessageQueue) {
            if([_startupMessageQueue count]>1)
                NSLog(@"bigger than one!!!!------------%d",[_startupMessageQueue count]);
            for (id queuedMessage in _startupMessageQueue) {
                [self _dispatchMessage:queuedMessage];
            }
            _startupMessageQueue = nil;
        }
        
        __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
        if (strongDelegate && [strongDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
            [strongDelegate webViewDidFinishLoad:webView];
        }
    }

     

  • 相关阅读:
    centos7.6进行软raid5制作
    面试题 PHP1
    基于githooks利用PHP_CodeSniffer做PSR2代码风格规范检测
    【GC 分代收集算法 VS 分区收集算法】
    【 Redis五大数据类型实现原理】
    【Java反射】
    【GC 垃圾收集器】
    【Redis过期策略/内存淘汰机制/对过期Key的处理】
    【Redis底层数据结构】
    【当骗子遇上研发工程师,还没开始就已经结束】
  • 原文地址:https://www.cnblogs.com/breezemist/p/3579028.html
Copyright © 2011-2022 走看看