WKWebView
@interface WKWebView : UIView
//重要属性
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;
@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
@property (nonatomic, readonly, nullable) SecTrustRef serverTrust;
@property (nullable, nonatomic, copy) NSString *customUserAgent;
@property (nonatomic) BOOL allowsLinkPreview;
@property (nonatomic, readonly, strong) UIScrollView *scrollView;
//加载方法
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL;
- (nullable WKNavigation *)goBack;
- (nullable WKNavigation *)goForward;
- (nullable WKNavigation *)reload;
- (nullable WKNavigation *)reloadFromOrigin;
//类方法
+ (BOOL)handlesURLScheme:(NSString *)urlScheme;
//与JS交互接口
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
//下面两个是iOS 14新引入API
- (void)evaluateJavaScript:(NSString *)javaScriptString inFrame:(nullable WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
- (void)callAsyncJavaScript:(NSString *)functionBody arguments:(nullable NSDictionary<NSString *, id> *)arguments inFrame:(nullable WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
WKBackForwardList
访问过的web页面历史记录。
WKNavigation
WKNavigation对象可以用来了解网页的加载进度。通过loadRequest、goBack等方法加载页面时,将返回一个WKNavigation对象。通过WKNavigationDelegate代理的以下几个方法,可知页面的加载情况。
//开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
//加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
//加载失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
WKNavigationDelegate
WKNavigationDelegate除了上述方法,还有一些重要的接口:
//在尝试加载内容之前调用,确定是否加载请求
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
//在请求响应后调用,决定是否加载内容,在这里可以针对特定HTTP状态码的处理
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
if (response.statusCode != 200) {
//非200状态码不加载
decisionHandler(WKNavigationResponsePolicyCancel);
return;
decisionHandler(WKNavigationResponsePolicyAllow);
//参考:Authentication Challenge的内容:/blog/137-ssl-pinning
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
WKNavigationAction
包含网页导航信息,需要据此显示对应的操作界面。
WKFrameInfo
标识当前网页内容信息的对象。
@interface WKFrameInfo : NSObject <NSCopying>
/*! @abstract A Boolean value indicating whether the frame is the main frame
or a subframe.
@property (nonatomic, readonly, getter=isMainFrame) BOOL mainFrame;
/*! @abstract The frame's current request.
@property (nonatomic, readonly, copy) NSURLRequest *request;
/*! @abstract The frame's current security origin.
@property (nonatomic, readonly) WKSecurityOrigin *securityOrigin API_AVAILABLE(macos(10.11), ios(9.0));
/*! @abstract The web view of the webpage that contains this frame.
@property (nonatomic, readonly, weak) WKWebView *webView API_AVAILABLE(macos(10.13), ios(11.0));
WKWebViewConfiguration
@interface WKWebViewConfiguration : NSObject <NSSecureCoding, NSCopying>
@property (nonatomic, strong) WKProcessPool *processPool;
/*! @abstract The preference settings to be used by the web view.
@property (nonatomic, strong) WKPreferences *preferences;
/*! @abstract The user content controller to associate with the web view.
@property (nonatomic, strong) WKUserContentController *userContentController;
/*! @abstract The website data store to be used by the web view.
@property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore API_AVAILABLE(macos(10.11), ios(9.0));
/*! @abstract The name of the application as used in the user agent string.
@property (nullable, nonatomic, copy) NSString *applicationNameForUserAgent API_AVAILABLE(macos(10.11), ios(9.0));
WKWebViewConfiguration表示初始化WKWebVie的配置信息。
WKProcessPool
@interface WKProcessPool : NSObject <NSSecureCoding>
WKProcessPool表示用于管理web内容的独立进程。WKWebView为了安全和稳定性考虑,会为每一个WKWebView实例分配独立的进程(而不是直接使用APP的进程空间),系统会有一个设定的进程个数上线。相同WKProcessPool对象的WKWebView共享相同的进程空间。这点也是WKWebView区别UIWebView的一个很大不同点。
可以看到WKProcessPool类没有暴漏任何接口,这意味着我们只能创建和读取该对象,通过对象地址判断是否在相同进程。
WKUserContentController
管理JavaScript 和 Web 视图的交互。WKUserScript代表一个需要注入到网页中的JavaScript脚本。
WKPreferences
偏好设置。
WKUIDelegate
处理和用户交互的代理。有三个方法需要重点说下:
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
以上三个方法会分别在web页面执行JavaScript的alert、confirm、prompt方法时被调用。
WKScriptMessageHandler和WKScriptMessageHandlerWithReply
@protocol WKScriptMessageHandler <NSObject>
@required
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
//iOS 14
@protocol WKScriptMessageHandlerWithReply <NSObject>
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message replyHandler:(void (^)(id _Nullable reply, NSString *_Nullable errorMessage))replyHandler;
WKScriptMessageHandler和WKScriptMessageHandlerWithReply是WKUserContentController暴露的代理协议,包含一个必须实现的方法,用于响应web的JavaScript代码发送的消息。
WKContentWorld
WKContentWorld是iOS 14的新增内容,可以理解为不同的命名空间不同的运行环境。显而易见的,在逻辑上,native APP的JS环境和web JS运行环境存在名称冲突的可能。WKContentWorld有两个类属性defaultClientWorld 、pageWorld,分别代表native APP和web容器的JS运行空间。开发者也可以通过:
+ (WKContentWorld *)worldWithName:(NSString *)name
工厂方法创建一个独立的JS运行环境。
WKWebView的基本使用
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
//注册处理器的名称
[userContentController addScriptMessageHandler:self name:@"kanchuanHandler"];
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = userContentController;
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
self.webView.UIDelegate = self;
[self.view addSubview:self.webView];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://kanchuan.com/blog"]];
[self.webView loadRequest:request];
native APP和JS交互
JS向native APP传递数据
通过addScriptMessageHandler注册唯一的name之后,在js代码中可以通过以下方式发送数据:
//js侧发送消息
let params = { "success": false }
window.webkit.messageHandlers.kanchuanHandler.postMessage(params)
//native APP接收消息
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"kanchuanHandler"]) {
NSDictionary *body = message.body;
native APP执行js代码
//在js侧定义方法
jsFunc = function(msg) {
console.log(msg)
return "ok"
//native APP执行js方法并获得返回结果
[self.webView evaluateJavaScript:@"jsFunc('hello world!')" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"result = %@", result);
WKWebView和JS交互同步问题
可以看到,使用window.webkit.messageHandlers.[name].postMessage的交互方式有时候并不好用,当需要在JS侧同步获取native APP的数据,然后才能继续执行JS代码时,就不能很好的实现需求。因为postMessag没有回调接口,无法将native APP的执行结果带回来。与此不同的,native APP执行js代码的evaluateJavaScript接口,则有completionHandler的回调,可以在native APP侧获取js的执行结果。
那么问题就来了:在JS侧执行postMessage时,如果拿到native APP的执行结果?
这个问题我记得在UIWebView时代并不存在,在WKWebView上却是个需要考虑的问题。目前,看川没有找到一种优雅的实现方案,提出的两种方案可供参考。
方案1:借助runJavaScriptTextInputPanelWithPrompt方法
上面介绍WKUIDelegate时提到的runJavaScriptTextInputPanelWithPrompt方法,这个方法本意是js在执行prompt方法时,给native APP一个自己实现prompt弹窗的时机,注意到这个方法有个completionHandler,即native APP处理完之后将数据返回给JS侧。
js prompt()方法用于显示用户进行输入的对话框。定义如下:
let msg = prompt(text, defaultText)
//text:标题文案
//defaultText:输入框默认文案
//返回用户输入的文案
当在WKWebView环境执行prompt方法时,会调用:
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
//针对特定prompt单独处理
if ([prompt isEqualToString:@"cmd"]) {
//将处理结果返回给JS。
completionHandler("result");
由于输入和返回都是字符串,可以通过JSON包装的形式扩展,这样就可以在js层调用特定名称的prompt同步拿到native APP的响应。
方案2:使用iOS 14新增的API
大概苹果也发现了这个问题,所以在iOS 14的系统中,针对WKWebView新增了很多优化的API,其中就包括针对addScriptMessageHandler的优化。新增了一个有replyHandler的didReceiveScriptMessage API。
[self.webView.configuration.userContentController addScriptMessageHandlerWithReply:self contentWorld:WKContentWorld.pageWorld name:@"kanchuanHandler"];
//WKScriptMessageHandlerWithReply
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message replyHandler:(void (^)(id _Nullable reply, NSString *_Nullable errorMessage))replyHandler {
replyHandler(@"success", nil);
在JS侧使用promise异步回调获取结果。
authSuccess = function() {
let params = { "result": true }
let promise = window.webkit.messageHandlers.kanchuanHandler.postMessage(params)
promise.then(
function(result) {
prompt('result', result)
function(err) {
console.log(err)
WKWebView addScriptMessageHandler循环引用
addScriptMessageHandler/addScriptMessageHandlerWithReply会强持有对象,需要在合适的时候进行removeScriptMessageHandlerForName操作,否则会造成循环引用。