美文网首页
浅谈iOS中的WKWebView和H5之间通信及WKWebVie

浅谈iOS中的WKWebView和H5之间通信及WKWebVie

作者: IBigLiang | 来源:发表于2020-05-14 19:59 被阅读0次

    大家好,今天笔者分享一下自己在项目开发中,对iOS和H5之间的通信的一些知识点。主要是针对于苹果APP混合开发中的一点点小知识。然后是对WKWebViewJavascriptBridge,这个iOS原生OC代码和H5之间的通信桥接第三方包的一些分析。
    首先在这里,先说明,这篇文章iOS这块的控件用的是WKWebview。因为再iOS12之后,UIWebview因为性能不足和内存占有过大等一系列问题,Apple公司最终决定将它慢慢移出iOS的舞台。
    先来大致看下WKWebview一些常规用到的代理方法:

    // --------------- WKNavigationDelegate -----------------
    // 请求之间,决定是否跳转网页:一般用户在点击一个链接时跳转到下一个页面之间会调用这个方法
    // 重点的代理方法,拦截webView 中url的方法
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
    
    // 获取响应数据之后,决定是否跳转
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
    
    // 开始加载页面时调用
    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
    
    // 主机地址被重定向时调用
    - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
    
    // 页面加载失败时调用
    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
    
    // 页面加载完毕时调用
    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
    
    // 跳转失败时调用
    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
    
    // 证书验证步骤:如果需要证书验证,与使用AFN进行HTTPS证书验证是一样的
    - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
    
    //  ------- WKUIDelegate方面一个比较重要的代理 ---------------
    // 在webview中使用alert函数时,需要执行这个代理,否则将无法打开
    // 坏处在于,需要我们硬性执行代理方法,好处在于,可以在我们原生iOS这边自定义弹出框
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
    
    

    接下来笔者说一下,最基础的iOS和js之间的通信方式,在WKWebview这边主要的方法:

    - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
    

    这里我们和UIWebview中执行通信代码的方法比较一下:

    - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
    

    可以看到,WKWebview这边的通信方法相对而言比较的人性化,因为iOS传递给js代码之后,得到的结果不一定是NSString类型,在很多情况下我们会需要得到一个对象、数组等等数据。
    接下来,顺便提一下一个iOS和H5之间通信的架包:JavaScriptCore.framework。这个架包是苹果自带支持的架包,主要目的就是让开发人员更加简便的去操作iOS和H5之间的通信代码。导入方式是直接从build phases中导入。如下图:

    image.png
    接下来,我们正式进入,对WKWebViewJavascriptBridge这个第三方包的使用和源码的分析。首先,笔者这边给出了一个简单的使用WKWebViewJavascriptBridge的demo,代码地址:https://github.com/IBIgLiang/WKWebView-WebViewJavascriptBridge
    。而WKWebViewJavascriptBridge这个第三方包的GitHub地址如下:https://github.com/marcuswestin/WebViewJavascriptBridge。里面已经很明确的指出的使用流程和注意事项。
    这里笔者用一张图来总结一下这个过程,当然这些代码在demo中都有,大家可以照着图看下:
    image.png
    接下来,我们重点来看看这个第三方的源代码,我们这里通过流程图,大致看下整个过程。先说明,笔者这里暂时不考虑UIWebview的操作,因为笔者已经说了它将退出。作为iOS开发,我们就从iOS这边开始整个流程,先从ViewController开始:
    image.png
    从上图中可以看出,一般来说,VC中所作的基本上都是准备工作,无论是注册通信还是响应通信,除了当响应通信时,没有需要传递的参数,因为WebViewJavascriptBridge本身所有的保存的参数的消息队列是统一处理的
    接下来我们来看看,上面说过的一个重要的代理:
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
    

    这个代理主要是做请求拦截操作,WebViewJavascriptBridge作者在拦截的过程中,加载WebViewJavascriptBridge_JS文件,然后是处理消息队列中的数据,具体我们还是一起看流程图:


    image.png

    这张图的发起点是在H5的JS端:

    function setupWebViewJavascriptBridge(callback) {
                if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
                if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
                window.WVJBCallbacks = [callback];
                // 创建一个看不见的iframe,发起一个请求,这个请求用来加载WebViewJavascriptBridge_JS.m中的js代码
                var WVJBIframe = document.createElement('iframe');
                WVJBIframe.style.display = 'none';
                WVJBIframe.src = 'https://__bridge_loaded__';
                document.documentElement.appendChild(WVJBIframe);
                setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
            }
    

    调用setupWebViewJavascriptBridge这个方法之后,通过iFrame发送https://bridge_loaded请求,进入decidePolicyForNavigationAction代理中,然后走上面流程图中的流程。当地址是https://bridge_loaded时,此时直接进入[_base injectJavascriptFile];代码段。具体内容如下:

    //TODO:WebViewJavascriptBridge桥接js文件的导入,并且发送iOS端业务数据给js
    - (void)injectJavascriptFile {
        NSString *js = WebViewJavascriptBridge_js();
        [self _evaluateJavascript:js];
        //导入桥接文件之后,将iOS端需要发送的数据发送给js端
        //self.startupMessageQueue中放置了iOS发起给js的数据
        if (self.startupMessageQueue) {
            NSArray* queue = self.startupMessageQueue;
            self.startupMessageQueue = nil;
            for (id queuedMessage in queue) {
                // 发送iOS端数据到JS端
                [self _dispatchMessage:queuedMessage];
            }
        }
    }
    

    主要做的就是导入js桥接问题,然后就是发送iOS端的业务参数,这个参数的来源已经在上面ViewController准备工作的流程图中写明。
    除了JS端的iFrame发送https://bridge_loaded请求之外,还有WebViewJavascriptBridge_js中的https://wvjb_queue_message,这个请求发起的主要作用就是处理消息队列中的信息。也就是[self WKFlushMessageQueue];这个步骤。这个消息队列的处理包含了JS端发送的数据。代码如下:

    //TODO:处理消息队列中的数据
    - (void)WKFlushMessageQueue {
        
        // 读取js发送的数据
        [_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
            if (error != nil) {
                NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
            }
            // 处理消息队列中的数据
            [_base flushMessageQueue:result];
        }];
    }
    

    [_base flushMessageQueue:result];这个方法中包含了JS端发送的数据和iOS处理完数据之后回调给JS端的数据的处理,包括两个部分:一个是iOS响应JS端通信后JS端回调给iOS端的消息内容,key值包含callbackId;另一个是JS响应iOS端的通信的消息内容,key值包含responseId。主要代码段:

    NSString* responseId = message[@"responseId"];
            if (responseId) {
                // 如果是js注册函数中回调回来的数据,就直接回到iOS的callHandler的block中
                // js注册registerHandler -> iOS发送相应业务数据callHandler -> js中注册函数的block回调到iOS中
                WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
                responseCallback(message[@"responseData"]);
                [self.responseCallbacks removeObjectForKey:responseId];
            } else {
                // iOS注册函数, js相应数据后,回到iOS注册函数registerHandler中的block,然后回调给js
                //iOS注册registerHandler -> js发送相应业务数据callHandler -> 进入iOS的registerHandler的block中 -> responseCallback回调给js
                WVJBResponseCallback responseCallback = NULL;
                NSString* callbackId = message[@"callbackId"];
                if (callbackId) {
                    responseCallback = ^(id responseData) {
                        if (responseData == nil) {
                            responseData = [NSNull null];
                        }
                        
                        WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                        // 将js发送给iOS之后,iOS处理完业务,重新将业务数据发送给js端
                        [self _queueMessage:msg];
                    };
                } else {
                    responseCallback = ^(id ignoreResponseData) {
                        // Do nothing
                    };
                }
                
                WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
                
                if (!handler) {
                    NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                    continue;
                }
                
                handler(message[@"data"], responseCallback);
    

    到此,除了桥接js文件没有涉及讲解之外,其余重要步骤都已讲解完毕。

    ============================================================

    接下来就是最后的WebViewJavascriptBridge_JS文件。这个js文件的内容其实很好理解,就是为iOS和JS两端的通信创建一个通用的WebViewJavascriptBridge对象:

    // 定义一个webview和js之间的桥
        window.WebViewJavascriptBridge = {
            // 用于JS端注册通信
            registerHandler: registerHandler,
            // 用于存储JS端相应iOS端的通信的参数,放入_fetchQueue中
            callHandler: callHandler,
            // 请求超时字段
            disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
            // 消息队列,用于放置JS端发送给iOS端的数据
            _fetchQueue: _fetchQueue,
            // 处理iOS端发送给JS端的数据
            _handleMessageFromObjC: _handleMessageFromObjC
        };
    

    这个文件里面的方法中,需要重点说明一下的就是_dispatchMessageFromObjC这个方法中的这个代码段:

    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({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
            };
        }
        
        var handler = messageHandlers[message.handlerName];
        if (!handler) {
            console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
        } else {
            handler(message.data, responseCallback);
        }
    }
    

    其实大家可以发现,这个代码段和笔者上面贴出来flushMessageQueue部分的那个代码段是相对应的,它就是JS端处理iOS发送过来的数据的过程。这里笔者就不具体展开了。
    由于篇幅长度原因,关于iOS中的WKWebView和H5之间通信及WKWebViewJavascriptBridge的源码分析就到此为止了。
    笔者将在下一篇文章WKURLSchemeHandler在WKWebView的拦截请求中的使用中指出NSURLProtocol拦截这WKWebView无效的问题以及原因(scheme底层注册有关),并且使用WKURLSchemeHandler拦截请求,当前有个前提是这个请求的scheme是自定义的,有兴趣的同学可以瞄一眼!

    相关文章

      网友评论

          本文标题:浅谈iOS中的WKWebView和H5之间通信及WKWebVie

          本文链接:https://www.haomeiwen.com/subject/dsktohtx.html