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