iOS 开发 之 JSBridge

2019-08-27  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读