H5页面与原生交互项目中经常遇到,今天做个小结。
从iOS原生的角度讲,我们可以使用UIWebView、WKWebView组件来展示H5页面,那么两者有什么区别呢?
讲述UIWebView只是让大家了解下这个历史,按现在的情况我们主要使用WKWebView
一、推出时间
UIWebView从iOS2.0推出到iOS12.0废弃,WKWebView从iOS8.0推出
UIWebView ios(2.0, 12.0) 、WKWebView ios(8.0)
UIWebView
UIKIT_EXTERN API_DEPRECATED("No longer supported; please adopt WKWebView.", ios(2.0, 12.0)) API_UNAVAILABLE(tvos, macos) @interface UIWebView : UIView <NSCoding, UIScrollViewDelegate>
WKWebView
#if TARGET_OS_IPHONE
WK_EXTERN API_AVAILABLE(macos(10.10), ios(8.0))
@interface WKWebView : UIView
#else
WK_EXTERN API_AVAILABLE(macos(10.10), ios(8.0))
@interface WKWebView : NSView
#endif
二、性能对比
UIWebView
占用过多内存,且内存峰值更是夸张。说白了就是性能低
WKWebView
1、网页加载速度快,内存消耗低。
2、更多的支持HTML5的特性,
3、高达60fps的滚动刷新率以及内置手势,
4、Safari相同的JavaScript引擎(但就这一点性能完胜),
5、增加了进度属性estimatedProgress等特性
三、交互
UIWebView
1、OC调用JS
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
2、JS调用OC
<1>iOS原生拦截URL
拦截到指定URL做响应操作
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType API_DEPRECATED("No longer supported.", ios(2.0, 12.0));
<2>使用JavaScriptCore实现(要注意循环引用的问题)
JSDelegate
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
/**
* H5会通过对象Bridge调用协议中的方法
*/
@protocol JSDelegate <JSExport>
// 关闭页面
- (void)finishPage;
@end
JSObject(中间对象,解决循环引用的问题,不直接让WebViewController遵守JSDelegate的原因)
#import <Foundation/Foundation.h>
#import "JSDelegate.h"
#import "WebViewController.h"
@interface JSObject : NSObject <JSDelegate>
@property(nonatomic, weak) WebViewController *h5VC;
@end
- (void)finishPage {
dispatch_async(dispatch_get_main_queue(), ^{
[self.h5VC pop];
});
}
WebViewController
@property (strong, nonatomic) JSContext *jsContext;
// 获取JSContext,并注入对象
- (void)creatJscontext
{
// 获取js上下文(JSContext)
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 遵守JSExport协议的对象(符合JSExport的对象都将转换为JavaScript包装器对象)
JSObject *obj = [[JSObject alloc] init];
obj.h5VC = self;
// 注入交互对象Bridge,H5通过Bridge对象调用JSExport中的方法
self.jsContext[@"Bridge"] = obj;
}
H5端将通过Bridge.finishPage()调用原生方法
备注:
JSContext
/*!
@interface
@discussion A JSContext is a JavaScript execution environment. All
JavaScript execution takes place within a context, and all JavaScript values
are tied to a context.
*/
JS_EXPORT API_AVAILABLE(macos(10.9), ios(7.0))
@interface JSContext : NSObject
UIWebView什么时机创建JSContext环境
什么时候UIWebView
会创建JSContext环境
,分两种方式,
第一在渲染网页时遇到<script标签
时,就会创建JSContext环境
去运行JavaScript代码
。
第二就是使用方法[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]
去获取JSContext环境
时,
这时无论是否遇到<script标签
,都会去创造出来一个JSContext环境
,而且和遇到<script标签
再创造环境是同一个。
JSExport
All objects that conform to JSExport convert to JavaScript wrapper objects,
even if they subclass classes that would otherwise behave differently. For
example, if a subclass of NSString conforms to JSExport, it converts to
JavaScript as a wrapper object rather than a JavaScript string.
*/
@protocol JSExport
WKWebView
1、OC调用JS
/* @abstract Evaluates the given JavaScript string.
@param javaScriptString The JavaScript string to evaluate.
@param completionHandler A block to invoke when script evaluation completes or fails.
@discussion The completionHandler is passed the result of the script evaluation or an error.
*/
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
2、JS调用OC
<1>iOS原生拦截URL
/*! @abstract Decides whether to allow or cancel a navigation.
@param webView The web view invoking the delegate method.
@param navigationAction Descriptive information about the action
triggering the navigation request.
@param decisionHandler The decision handler to call to allow or cancel the
navigation. The argument is one of the constants of the enumerated type WKNavigationActionPolicy.
@discussion If you do not implement this method, the web view will load the request or, if appropriate, forward it to another application.
*/
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
<2>使用MessageHandler(WKScriptMessageHandler)实现
WKWebView 初始化时,有一个参数叫configuration,
它是WKWebViewConfiguration类型的参数,而WKWebViewConfiguration有一个属性叫userContentController,
它又是WKUserContentController类型的参数。
WKUserContentController对象有一个方法- addScriptMessageHandler:name:,我把这个功能简称为MessageHandler。
添加或者删除消息处理
/*! @abstract Adds a script message handler.
@param scriptMessageHandler The message handler to add.
@param name The name of the message handler.
@discussion Adding a scriptMessageHandler adds a function
window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
frames.
*/
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
/*! @abstract Removes a script message handler.
@param name The name of the message handler to remove.
*/
- (void)removeScriptMessageHandlerForName:(NSString *)name;
收到H5发送的消息
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class WKScriptMessage;
@class WKUserContentController;
/*! A class conforming to the WKScriptMessageHandler protocol provides a
method for receiving messages from JavaScript running in a webpage.
*/
@protocol WKScriptMessageHandler <NSObject>
@required
/*! @abstract Invoked when a script message is received from a webpage.
@param userContentController The user content controller invoking the
delegate method.
@param message The script message received.
*/
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
@end
H5端调用
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
综上所述:
最优的方案就是使用WKWebView,交互使用MessageHandler