1 通信原理
除了JavaScriptCore,JS调用原生只能异步处理返回值
1.1 URL拦截(JS调用原生) + 解析JS(原生调用JS)
1.1.1 基于生命周期的拦截
基于URL协议,JS发送请求,在WebView生命周期方法
中拦截到如myURLProtocol://
。 这种方法对于UIWebView和WKWebView通用。
- 1 拦截:针对JS调用原生。 如果JS需要返回值,需要在参数里加上回调函数(或根据协议,约定好怎么回调),然后通过原生调用JS的方式,把返回值传回去。
//WKWebView
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSURL *URL = navigationAction.request.URL;
NSString *scheme = [URL scheme];
if ([scheme isEqualToString:@"myURLProtocol"]) {
[self handleCustomAction:URL]; //自己解析协议
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
//UIWebView
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = request.URL;
NSString *scheme = [URL scheme];
if ([scheme isEqualToString:@"myURLProtocol"]) {
[self handleCustomAction:URL]; //自己解析协议
return NO;
}
return YES;
}
复制代码
- 2 解析:针对原生调用JS
//WKWebView
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@----%@",result, error);
}];
//UIWebView
[_webView stringByEvaluatingJavaScriptFromString:jsStr]; //需要自己另外处理回调
复制代码
iOS下JS与OC互相调用(一)--UIWebView 拦截URL iOS下JS与OC互相调用(二)--WKWebView 拦截URL
###1.1.2 基于NSURLProtocol的拦截
- 继承NSURLProtocol,并注册子类。
- 重写+ (BOOL)canInitWithRequest:(NSURLRequest *)request等方法
- 对于WK,没有官方支持,下文2方案也不完善。
iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求
iOS WKWebView (NSURLProtocol)拦截js、css,图片资源
1.2 基于JavaScriptCore,只适用UIWebView
见第2节
1.3 基于MessageHandle,只适用WKWebView
见第3节
2 基于JavaScriptCore
2.1 JavaScriptCore的构成
JS的引擎,原生和JS互相访问
最早是WebKit中解释执行JS,作用相当于Chrome的V8
- JSVirtualMachine 每个虚拟机对应原生的一个线程。 每个虚拟机有自己的GC,多个虚拟机之间的内存无法共享。
- JSContext 代表JS的上下文环境(浏览器中的一个tab?) 可以从UIWebView中获取,也可以从本地JS文件中读取。
- JSValue 原生调用JS的关键, 通过Context获取到JSValue,可以代表js的一个变量、函数
- JSExport协议 JS调用原生的关键,原生通过JSExport协议给JS提供接口。
2.2 实例
2.2.1 原生访问JS的变量,通过context拿到变量i
JSContext *context = [[JSContext alloc] init];
// 解析执行 JavaScript 脚本
[context evaluateScript:@"var i = 4 + 8"];
// 转换 i 变量为原生对象
NSNumber *number = [context[@"i"] toNumber];
NSLog(@"var i is %@, number is %@",context[@"i"], number);
复制代码
2.2.2 原生访问JS的函数,将JSValue映射到context中的一个函数。
// 解析执行 JavaScript 脚本
[context evaluateScript:@"function addition(x, y) { return x + y}"];
// 获得 addition 函数
JSValue *addition = context[@"addition"];
// 传入参数执行 addition 函数
JSValue *resultValue = [addition callWithArguments:@[@(4), @(8)]];
// 将 addition 函数执行的结果转成原生 NSNumber 来使用。
NSLog(@"function is %@; reslutValue is %@",addition, [resultValue toNumber]);
复制代码
2.2.3 JS访问原生,通过JSExport协议
@protocol YYYJSExports <JSExport>
-(void)record:(NSString *)msg;
@end
@interface YYYUIWebViewJSBridge()<YYYJSExports>
@property (nonatomic, strong) JSContext *jsContext;
@end
@implementation YYYUIWebViewJSBridge
+ (void)registWebView:(UIWebView *_Nonnull)webView withId:(NSString *_Nonnull)webViewId {
if(!webView || !webViewId) return;
SHWUIWebViewJSBridge *bridge = [SHWUIWebViewJSBridge new];
bridge.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
bridge.jsContext[@"shw_analytics"] = bridge;
[bridgeContainer setObject:webView forKey:webViewId];
}
+ (void)deRegistWebViewWithId:(NSString *_Nonnull)webViewId {
if(!webViewId) return;
[bridgeContainer removeObjectForKey:webViewId];
}
- (void)record:(NSString *)msg {
SHWEvent *event = [SHWEvent initEventWithId:_SW_ANALYTICS_EVENTID_WEBVIEW args:@{__SW_KEY_WebView: msg}];
[SHWMsgQueue logEvent:event];
}
复制代码
3 基于MessageHandler
MessageHandler,作用类似于JSExport,为JS调用原生提供接口。 但原生调用JS,不能像JSContext那样直接调用,而是通过通过evaluateJS。
iOS下JS与OC互相调用(三)--MessageHandler
3.1 原生调用JS
NSString *jsStr2 = @"window.ctuapp_share_img";
[self.webView evaluateJavaScript:jsStr2 completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@----%@",result, error);
}];
复制代码
3.2 JS调用原生
//viewWillAppear:
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"ScanAction"];
//viewWillDisappear:
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"ScanAction"];
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
// message.body -- Allowed types are NSNumber, NSString, NSDate, NSArray,NSDictionary, and NSNull.
NSLog(@"body:%@",message.body);
if ([message.name isEqualToString:@"ScanAction"]) {
NSLog(@"扫一扫");
} else if ([message.name isEqualToString:@"Location"]) {
[self getLocation];
} else if ([message.name isEqualToString:@"Share"]) {
[self shareWithParams:message.body];
}
}
复制代码