美文网首页
iOS 开发 之 JSBridge

iOS 开发 之 JSBridge

作者: 黄成瑞 | 来源:发表于2019-08-27 18:04 被阅读0次

    简单了解下:

    一、iOS开发时使用到的两个WebView
    1.UIWebView : 使用JavaScriptCore进行JS和OC的交互。
    交互原理:通过一个JSContext获取UIWebView的JS执行上下文,然后通过这个上下文进行JS和OC交互。
    _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        _jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
            NSLog(@"%@",@"获取 WebView JS 执行环境失败了!");
        };
    2.WKWebView : 使用WKUserContentController进行JS和OC的交互。
    交互原理:
    通过userContentController把需要观察的JS执行函数注册起来
    然后通过一个协议方法将所有注册过的JS函数执行的参数传递到此协议方法中国呢。
    步骤:
    注册 需要 观察的 JS 执行函数
     [webView.configuration.userContentController addScriptMessageHandler:self name:@"jsFunc"];
    
    在 JS 中调用这个函数并传递参数数据
    window.webkit.messageHandlers.jsFunc.postMessage({name : "李四",age : 22});
    
    OC 中遵守 WKScriptMessageHandler 协议。
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message 
    
    
    此协议方法里的 WKScriptMessage 有 name & body 两个属性。 name 可以用来判断是哪个 JSFunc 调用了。body 则是 JSFunc 传递到 OC 的参数。
    
    
    二、Hybrid APP
    1.Hybrid APP : 混合模式移动应用(Web-app、Native-app),说白了就是介于这两者之间的APP
    2.Web-app :是一种基于Web的系统和应用(优点:跨平台开发的优势)
    3.Native-app :是一种基于智能手机本地操作系统(如iOS、Android、WP)并使用原生程式编写运行的第三方应用程序,也叫做本地APP(对应语言:Objective-C、JAVA、C++)(优点:良好的用户交互体验优势)
    
    三、WebViewJSBridge框架
    1.WebViewJSBridge : 是一个OC和JS交互的桥接机制,主要包含三个类,JS端的Window.WebViewJavascriptBridge,OC端的WebViewJavascriptBridge和WebViewJavascriptBridgeBase。桥接类支持JS调用OC的方法、OC调用JS方法、JS调用OC通过重定向url并取handlerName来调用、OC调用JS通过stringByEvaluationJavaScriptFromString调用。
    

    JSBridge是什么?有什么作用?

    JSBridge是JS和Native Code两者交互的一座连接桥梁(桥接),可以让两端相互嗲用
    说白了就是我们经常所提到的JS与OC的交互啦
    

    一、JSBridge实现方案
    1.Iframe(WebViewJavascriptBridge和Cordava都采用了Iframe这个方案)

    核心思路:
    UIWebView拦截Iframe的src,双方提前约定好协议,例如https://__jsbridge__就是一次调用的开始
    
    实现:
    1.JS暴露一个方法给Native,接受结果
    function responseFromObjC(response) {
      if (!callback) {
        return;
      }
      callback(response);
    }
    2.Native实现UIWebView的代理
    在如下代理方法拦截请求,识别到特定的URL,开始一次调用流程
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(YUWebViewNavigationType)navigationType {
      NSURL *url = request.URL;
      // 判断URL是否是JSBridge调用
      if ([url.host isEqualToString:@"__jsbridge__"]) {
        // 处理JS调用Native
        return NO;
      }
      return YES;
    }
    
    3.JS开启一个Iframe,加载一个特定的URL,开始一次调用
    var iframe = document.createElement('iframe');
    iframe.style.display = 'none'
    iframe.src = 'https://_jsbridge__?action=' + action + '&data=' + data;
    document.documentElement.appendChild(iframe);
    
    4.Native方法执行完成后,调用JS方法responseFromObjC将结果回传给JS。
    // 获取调用参数,demo的调用方式是:'https://__jsbridge__?action=action&data='
    // 参数直接放在query里面的,更好的方案是js暴露一个方法给原生,原生调用方法获取数据
    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES];
    NSArray *queryItems = urlComponents.queryItems;
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    for (NSURLQueryItem *queryItem in queryItems) {
        NSString *key = queryItem.name;
        NSString *value = queryItem.value;
        [params setObject:value forKey:key];
    }
    NSString *action = params[@"action"];
    NSString *data = params[@"data"];
    
    if ([action isEqualToString:@"alertMessage"]) {
        // 调用原生方法,获取数据
        // js暴露方法`responseFromObjC`给原生,原生通过该方法回调
        // 在实际项目中,为了实现实现js并发原生方法,最好带一个callBackID,来区分不同的调用
        [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"responseFromObjC('%@')", data]];
    } else {
        [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"responseFromObjC('Unkown action'"]];
    }
    

    2.Ajax实现方案

    核心思路:
    JS使用XMLHttpRequest发起请求,在Native拦截达到调用的目的,通过自定义NSULRProtocol可以拦截到Ajax请求
    
    实现:
    1.新建类继承自NSURLProtocol,并注册。
    [NSURLProtocol registerClass:[CRURLProtocol class]];
    
    2.实现自定义NSURLProtocol,在startLoading方法拦截Ajax请求
    - (void)startLoading {
        NSURL *url = [[self request] URL];
        // 拦截“http://__jsbridge__”请求
        if ([url.host isEqualToString:@"__jsbridge__"]) {
           // 处理JS调用Native
        }
    }
    
    3.JS发起Ajax请求,URL为提前约定的特殊值,例如:http://jsbridge。请求参数放在Request Body里。
    // 调用原生
    function callNative(action, data) {
            var xhr = new window.XMLHttpRequest(),
            url = 'http://__jsbridge__';
            xhr.open('POST', url, false);
            xhr.send(JSON.stringify({
                        action: action,
                        data: data
                        }));
            return xhr.responseText;
    }
    
    4.Naive拦截到请求,获取参数,执行Native方法,最后通过Ajax的Response把结果返回给JS。
    ...
    // 2. 从HTTPBody中取出调用参数
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:self.request.HTTPBody options:NSJSONReadingAllowFragments error:nil];
    NSString *action = dic[@"action"];
    NSString *data = dic[@"data"];
    NSData *responseData;
    
    // 3. 根据action转发到不同方法处理,param携带参数
    if ([action isEqualToString:@"alertMessage"]) {
        responseData = [data dataUsingEncoding:NSUTF8StringEncoding];
    } else {
        responseData = [@"Unknown action" dataUsingEncoding:NSUTF8StringEncoding];
    }
    
    // 4. 处理完成,将结果返回给js
    [self sendResponseWithResponseCode:200 data:responseData mimeType:@"text/html"];
    ...
    
    - (void)sendResponseWithResponseCode:(NSInteger)statusCode data:(NSData*)data mimeType:(NSString*)mimeType {
        NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:[[self request] URL] statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:@{@"Content-Type" : mimeType}];
        
        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
        if (data != nil) {
            [[self client] URLProtocol:self didLoadData:data];
        }
        [[self client] URLProtocolDidFinishLoading:self];
    }
    

    3.JSCore方案

    前两种方案虽然方法不一致,但思路都是类似的,由于JS不能直接调用Native方法,通过曲线救国的方式,找到一个载体来传递消息。
    而这种方法就比较直接了,使用iOS7推出的黑科技JavaScriptCore,将Native方法直接暴露给JS,打通两端的数据通道。说到JavaScriptCore,不得不说的是JSPatch、ReactNative、Weex等都是利用JavaScriptCore来实现各种炫酷的功能。
    
    核心思路:
    
    实现:
    1.
    - (void)injectJSBridge {
        // 获取JSContext
        JSContext *context = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        // 给JS注入方法callNative
        context[@"callNative"] = ^(JSValue *action, JSValue *data) {
            NSString *actionStr = [action toString];
            NSString *dataStr = [data toString];
            if ([actionStr isEqualToString:@"alertMessage"]) {
                return dataStr;
            } else {
                return @"Unkown action";
            }
        };
    }
    
    2.JS调用
    callNative("alertMessage", "Hello world!")
    

    二、性能对比
    JSCore > Ajax > Iframe

    三、WebViewJavaScriptBridge

    WebViewJavaScriptBridge是用于WkWebView和UIWebView中JS和OC交互的。
    
    基本原理:
    1.把OC的方法注册到桥梁中,让JS去调用
    2.把JS的方法注册到桥梁中,让OC去调用
    
    使用步骤:
    1.首先在项目中倒入WebViewJavaScriptBridge框架。(pod 'WebViewJavascriptBridge')
    2.导入头文件#import <WebViewJavascriptBridge.h>
    3.建立WebViewJavaScriptBridge和WebView之间的关系(_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];)
    4.在HTML文件中,复制粘贴以下两端JS函数
    function setupWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
            if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
            window.WVJBCallbacks = [callback]; // 创建一个 WVJBCallbacks 全局属性数组,并将 callback 插入到数组中。
            var WVJBIframe = document.createElement('iframe'); // 创建一个 iframe 元素
            WVJBIframe.style.display = 'none'; // 不显示
            WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; // 设置 iframe 的 src 属性
            document.documentElement.appendChild(WVJBIframe); // 把 iframe 添加到当前文导航上。
            setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
        }
        
        // 这里主要是注册 OC 将要调用的 JS 方法。
        setupWebViewJavascriptBridge(function(bridge){
           
        });
    5.往桥梁中注入OC方法
     [_jsBridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {
            NSLog(@"dataFrom JS : %@",data[@"data"]);
            
            responseCallback(@"扫描结果 : www.baidu.com");
        }];
    这段代码的意思:
    
    scanClick 是 OC block 的一个别名。
    block 本身,是 JS 通过某种方式调用到 scanClick 的时候,执行的代码块。
    data ,由于 OC 这端由 JS 调用,所以 data 是 JS 端传递过来的数据。
    responseCallback OC 端的 block 执行完毕之后,往 JS 端传递的数据。
    6.往桥梁中注入JS函数(需要在第二段JS代码中,注入JS的函数)
    // 这里主要是注册 OC 将要调用的 JS 方法。
        setupWebViewJavascriptBridge(function(bridge){
            // 声明 OC 需要调用的 JS 方法。
            bridge.registerHanlder('testJavaScriptFunction',function(data,responseCallback){
                // data 是 OC 传递过来的数据.
                // responseCallback 是 JS 调用完毕之后传递给 OC 的数据
                alert("JS 被 OC 调用了.");
                responseCallback({data: "js 的数据",from : "JS"});
            })
        });
    
    这段代码的意思:
    
    testJavaScriptFunction 是注入到桥梁中 JS 函数的别名。以供 OC 端调用。
    回调函数的 data。 既然 JS 函数由 OC 调用,所以 data 是 OC 端传递过来的数据。
    responseCallback 。 JS 调用在被 OC 调用完毕之后,向 OC 端传递的数据。
    说白了就是OC端注册OC方法然后调用JS函数,JS端注册JS函数然后调用OC方法
    

    参考文章
    聊聊iOS开发中的JSBridge

    https://www.jianshu.com/p/d12ec047ce52
    https://www.jianshu.com/p/d12ec047ce52

    相关文章

      网友评论

          本文标题:iOS 开发 之 JSBridge

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