第一部分原理解析
UIWebView
UIWebView,苹果在iOS2.0推出。
** UIWebView与JS交互原理:**
H5->通讯协议->原生
原生->回调函数->H5
** 理解:**
- 交互之前,制定好H5和原生的交互通讯协议:类似于Http协议;
- H5通过触发能被原生监听并捕获截拦的H5行为,原生通过Web代理拦截,获取通讯协议,解析匹配后会路由到具体处理方法,执行原生能力逻辑,比如业务逻辑处理,或者界面跳转。
-
原生在处理完数据的时机把数据通过API回调传递给H5,让H5做相应的展示。
能被原生监听并捕获截拦的H5行为: console.log、alert、confirm、prompt、location.href 等。 举例:location.href ='jsbridge://method?a=2&b=3#h5MethodTag' 协议名:jsbridge://,app 自定义的协议名,用于H5触发行为的监控捕获; 接口路径:method,原生具体能力路径,不同原生能力路径不同; 参数:a=2&b=3 回调:h5MethodTag,原生回调H5的方法标识;
** 第一部分:H5->原生 **
通过shouldStartLoadWithRequest代理方法拦截H5事件,解析协议路由到具体方法。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
** 第二部分:原生->H5 **
在webViewDidFinishLoad的时机,通过stringByEvaluatingJavaScriptFromString回调JS函数,传递,或者更改H5界面元素。
- (void)webViewDidFinishLoad:(UIWebView *)webView;//UIWebView代理方法
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;//UIwebView的实例方法,可以获取H5界面元素,调用原有的JS方法,也可注入新的JS代码,调用新注入的代码
关于如何解藕H5,实现更好的跨平台,查阅文章 解耦—Hybrid H5跨平台性思考
WKWebView
iOS8以后,苹果推出了新框架Wekkit,提供了替换UIWebView的组件WKWebView。
** 第一部分:H5->原生 **
JavaScript调用原生
window.webkit.messageHandlers.jsCallOC.postMessage(dict);
原生响应调用,需要原生先通过WKUserContentController注册方法。WKUserContentController还有一个功能注入js。
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:self name:@"jsCallOC"];
原生遵循WKScriptMessageHandler代理处理路由
#pragma mark - WKScriptMessageHandler代理处理JS调用OC
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"方法名:%@", message.name);
NSLog(@"参数:%@", message.body);
// 方法名
NSString *methods = [NSString stringWithFormat:@"%@:", message.name];
SEL selector = NSSelectorFromString(methods);
// 调用方法
if ([self respondsToSelector:selector]) {
[self performSelector:selector withObject:message.body];
} else {
NSLog(@"未实行方法:%@", methods);
}
}
** 第二部分:原生->H5 **
原生调用JS,javaScriptString是js的函数名,evaluateJavaScript是WKWebView的实例方法。
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
原生还提供监听Prompt,alert,Confirm的代理方法
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
}
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
}
上面两个webView的单单从与H5交互这个角度进行对比,很多功能UIWebView和WKWebView都可以实现,UIWebView需要处理更多的细节,WKWebView的封装性更好,而且提供了更多的方法可以调用。
上面都是通过iOS原生控件直接加载web界面,使用控件的原生方法和H5进行交互,下面将使用iOS原生框架*JavaScriptCore*框架,和第三方框架*WebViewJavascriptBridge*来实现与H5的交互WebViewJavascriptBridge
WebViewJavascriptBridge提供了对UIWebView和WKWebView两个原生控件的支持。只需要根据控件选择对应的bridge即可,交互API基本不变。
** 第一部分:H5->原生 ** H5通过bridge.callHandler 或者 bridge.send触发事件调用原生,原生通过下面代码中注册的方法,响应H5的调用。
_bridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
[_bridge registerHandler:@"handleName" handler:^(id data, WVJBResponseCallback responseCallback) {
//JS调用原生,原生响应处理逻辑。
}];
**第二部分:原生->H5 **
原生通过👇三个方法调用JS方法,handlerName可以是JS的函数名,data是参数
- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;
JavaScriptCore
JavaScriptCore框架是苹果在iOS7.0推出的iOS和JS交互的新方案,更加面向对象。UIWebView和WKWebKit都可使用JavaScriptCore框架。注意,** 听说 **WKWebView中鉴于一些线程和进程的问题有无法获取JSContext的现象。
** 第一部分:H5->原生 **
JS调用原生的方法
<input type="button" value="Call ObjC system camera" onclick="OCModel.callSystemCamera()">
<input type="button" value="Call ObjC system alert" onclick="OCModel.showAlertMsg('js title', 'js message')">
原生需要实现OCModel对象的绑定
- 声明一个继承JSExport协议的协议JavaScriptObjectiveCDelegate,并且声明让JS调用的方法
-
创建一个类遵循JavaScriptObjectiveCDelegate协议的对象
@interface NLJsObjCModel : NSObject
@property (nonatomic, weak) JSContext *jsContext;
@property (nonatomic, weak) UIWebView *webView;
@end
-
实现绑定
NLJsObjCModel *model = [[NLJsObjCModel alloc] init]; self.jsContext[@"OCModel"] = model; model.jsContext = self.jsContext; model.webView = self.webView;
JavaScriptCore框架使用这部分代码来源于IOS 原生与HTML交互
** 第二部分:原生->H5 **
获取JSContext对象,对象调用evaluateScript:获取JSValued对象,最终实现与JS的交互。
- (void)webViewDidFinishLoad:(UIWebView *)webView {
JSContext *jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSValue *jsValue = [jsContext evaluateScript:@"showAppAlertMsg"];
[jsValue callWithArguments:@[@"这是app本地交互文案"]];
}
总结
这些方法都可以实现交互,UIWebView需要指定协议,处理更多细节;WKWebView和WebViewJavascriptBridge的使用模式很像,可以看出相比于UIWebView的交互流程已经简洁很多;WebViewJavascriptBridge提供了对UIWebView和WKWebView的支持,而且和JS的交互过程很简洁。JavaScriptCore是实现了面向协议的封装,看起来稍微复杂,但是代码的封装性会更好。
很多公司的老代码还是比较多的使用UIWebView的自定义协议拦截的方法,所以这方面的使用需要了解;对于新的代码,还是希望可以采用JavaScriptCore或者WebViewJavascriptBridge 结合WKWebView的方式。