iOS中的WebView

2,744 阅读14分钟

UIWebView

使用方式与普通View一样。内置一个UIScrollView。

需要设置UIWebViewDelegate。delegate只有四个回调方法:是否开始,load开始,load完成,load失败。

HTML

[webView loadHTMLString:@"<html><body><p>this is baidu!</p></body></html>" baseURL:];

HTML File

NSString *str = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSURL *url = [NSURL fileURLWithPath:str];
[webView loadFileURL:url allowingReadAccessToURL:url];

NSData

NSString *path = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:path];
[webView loadData:data MIMEType:@"image/gif" textEncodingName:@"UTF-8" baseURL:url];
webView.userInteractionEnabled = NO;

设置MIMEType为指定类型

@"image/jpg"
@"image/gif"
@"application/pdf"

URLRequest

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[webView loadRequest:request];

UIWebViewDelegate

// 是否运行load
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
    
    return YES;
}

// 开始load
- (void)webViewDidStartLoad:(UIWebView *)webView {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

// load完成
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

// load失败
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

关键词

  1. 属于App占用内存,内存消耗过大可能导致app崩溃。
  2. 不如WKWebView灵活,提供的delegate回调方法很少。

WKWebView

加载HTML,NSData与UIWebView一致。

独立的进程,network loading和ui rendering多进程同时允许,类似Chrome。所以App的内存消耗大幅下降,而其他进程的内存消耗增加。

内存消耗过大,UIWebView会导致app被杀死,而WKWebView的web content process被杀死则会出现白屏。

有WKNavigationDelegate和WKUIDelegate两个。

alert弹窗:实现WKUIDelegate协议的runJavaScriptAlertPanelWithMessage方法。

confirm弹窗:实现WKUIDelegate协议的runJavaScriptConfirmPanelWithMessage方法。

WKWebViewConfiguration

使用WKWebView的默认初始化方法:

- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration;

会将configuration对象复制一份,之后再去修改configuration则不生效。

WKNavigationDelegate

自定义webView接受、加载、完成请求等过程的行为。

  • WKNavigation对象包含了跟踪页面加载过程的信息。
  • WKNavigationAction对象包含了可能导致一次加载的操作的信息,用于制定策略决策。
  • WKNavigationResponse对象包含用于制定策略决策的浏览响应信息

关键的,与UIWebView不同的是:

/*! A class conforming to the WKNavigationDelegate protocol can provide
 methods for tracking progress for main frame navigations and for deciding
 policy for main frame and subframe navigations.
 */

// MARK: 拦截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.
 */
// 接收到响应后, 如何处理(是否跳转, 是否拦截URL等)
// 调用decisionHandler(WKNavigationActionPolicyAllow);则允许跳转
// 调用decisionHandler(WKNavigationActionPolicyCancel);即不允许跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
    // 该方法实现了, 则必须调用decisionHandler.
    
    NSURL *url = navigationAction.request.URL;
    
    if ([url.absoluteString isEqualToString:@"about:blank"]) {
        decisionHandler(WKNavigationActionPolicyAllow);
        return;
    }
    
    NSString *scheme = [url scheme];
    
    NSURL *docUrl = navigationAction.request.mainDocumentURL;
    NSURL *urlOfBundleFilePrefix = [docUrl URLByDeletingLastPathComponent];
    NSString *route = [url.absoluteString stringByReplacingOccurrencesOfString:urlOfBundleFilePrefix.absoluteString withString:@""];
    
    
    if ([route containsString:@"actionOC_JS://"]) {
        [self routeJS_OC_Action:route];
        
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    
    NSString *host = [url host];
    
    NSLog(@"comes from %@ %@", scheme, host);
    
    if ([host containsString:@"baidu"]) {
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    
    decisionHandler(WKNavigationActionPolicyAllow);
}

/*! @abstract Decides whether to allow or cancel a navigation after its
 response is known.
 @param webView The web view invoking the delegate method.
 @param navigationResponse Descriptive information about the navigation
 response.
 @param decisionHandler The decision handler to call to allow or cancel the
 navigation. The argument is one of the constants of the enumerated type WKNavigationResponsePolicy.
 @discussion If you do not implement this method, the web view will allow the response, if the web view can show it.
 */
//- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
//    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
//}

/*! @abstract Invoked when a main frame navigation starts.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
// 页面开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

/*! @abstract Invoked when a server redirect is received for the main
 frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
// 接收到服务器的重定向
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

/*! @abstract Invoked when an error occurs while starting to load data for
 the main frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 @param error The error that occurred.
 */
// 页面加载失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
    NSLog(@"error: %@", error);
}

/*! @abstract Invoked when content starts arriving for the main frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
// 跳转已经完成, 页面内容开始到达并展示
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

/*! @abstract Invoked when a main frame navigation completes.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
// 页面跳转完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

/*! @abstract Invoked when an error occurs during a committed main frame
 navigation.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 @param error The error that occurred.
 */
// 页面跳转失败
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

/*! @abstract Invoked when the web view needs to respond to an authentication challenge.
 @param webView The web view that received the authentication challenge.
 @param challenge The authentication challenge.
 @param completionHandler The completion handler you must invoke to respond to the challenge. The
 disposition argument is one of the constants of the enumerated type
 NSURLSessionAuthChallengeDisposition. When disposition is NSURLSessionAuthChallengeUseCredential,
 the credential argument is the credential to use, or nil to indicate continuing without a
 credential.
 @discussion If you do not implement this method, the web view will respond to the authentication challenge with the NSURLSessionAuthChallengeRejectProtectionSpace disposition.
 */
//- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
//    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
//}

/*
 这个方法比较关键,web content process因内存消耗过大被杀死,则此回调会触发。在其中可以对webView进行reload操作。
*/
/*! @abstract Invoked when the web view's web content process is terminated.
 @param webView The web view whose underlying web content process was terminated.
 */
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

其中:

/**
 在decidePolicyForNavigationAction方法中,
 根据navigationAction的一些参数, 解析出对应的action和参数, 执行即可.
 
 对于这些action:
 若调用iOS系统的一些接口, 则over.
 若准备一些参数, 再传递给JS中的函数, 则依然需要调用evaluateJavaScript方法.
 */
- (void)routeJS_OC_Action:(NSString *)route {
    NSString *action = [route stringByReplacingOccurrencesOfString:@"actionOC_JS://" withString:@""];
    if ([action containsString:@"setColor"]) {
        NSString *paramStr = [action stringByReplacingOccurrencesOfString:@"setColor?" withString:@""];
        NSArray *params = [paramStr componentsSeparatedByString:@"&"];
        
        NSMutableDictionary *colorDic = [NSMutableDictionary dictionary];
        for (NSString *colorStr in params) {
            NSArray *key_value = [colorStr componentsSeparatedByString:@"="];
            [colorDic setObject:key_value[1] forKey:key_value[0]];
        }
        CGFloat r = [[colorDic objectForKey:@"r"] floatValue];
        CGFloat g = [[colorDic objectForKey:@"g"] floatValue];
        CGFloat b = [[colorDic objectForKey:@"b"] floatValue];
        CGFloat a = [[colorDic objectForKey:@"a"] floatValue];

        UIColor *color = [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a];
        [self p_actionSetColor:color];
    } else if ([action containsString:@"getLocation"]) {
        [self p_actionGetLocation];
    } else if ([action containsString:@"share"]) {
        [self p_actionShare];
    } else if ([action containsString:@"scan"]) {
        
    } else if ([action containsString:@"shake"]) {
        
    } else if ([action containsString:@"pay"]) {
    
    }
}

WKUIDelegate

提供为网页展示native用户界面的方法。WebView用户界面通过实现这个协议来控制新窗口的打开,增强用户单击元素时显示的默认菜单项的表现,并执行其他用户界面相关的任务。这些方法可以通过处理JavaScript或其他插件内容来调用。默认每个WebView一个窗口,如果需要实现一个非常规用户界面,需要依靠WKUIDelegate来实现。

关键的,与UIWebView不同的是:

/*! A class conforming to the WKUIDelegate protocol provides methods for
 presenting native UI on behalf of a webpage.
 */

/*! @abstract Creates a new web view.
 @param webView The web view invoking the delegate method.
 @param configuration The configuration to use when creating the new web
 view.
 @param navigationAction The navigation action causing the new web view to
 be created.
 @param windowFeatures Window features requested by the webpage.
 @result A new web view or nil.
 @discussion The web view returned must be created with the specified configuration. WebKit will load the request in the returned web view.
 
 If you do not implement this method, the web view will cancel the navigation.
 */
//- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;

/*! @abstract Notifies your app that the DOM window object's close() method completed successfully.
 @param webView The web view invoking the delegate method.
 @discussion Your app should remove the web view from the view hierarchy and update
 the UI as needed, such as by closing the containing browser tab or window.
 */
- (void)webViewDidClose:(WKWebView *)webView {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

/*! @abstract Displays a JavaScript alert panel.
 @param webView The web view invoking the delegate method.
 @param message The message to display.
 @param frame Information about the frame whose JavaScript initiated this
 call.
 @param completionHandler The completion handler to call after the alert
 panel has been dismissed.
 @discussion For user security, your app should call attention to the fact
 that a specific website controls the content in this panel. A simple forumla
 for identifying the controlling website is frame.request.URL.host.
 The panel should have a single OK button.
 
 If you do not implement this method, the web view will behave as if the user selected the OK button.
 */
// 弹出警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    
    [self presentViewController:alert animated:YES completion:nil];
}

/*! @abstract Displays a JavaScript confirm panel.
 @param webView The web view invoking the delegate method.
 @param message The message to display.
 @param frame Information about the frame whose JavaScript initiated this call.
 @param completionHandler The completion handler to call after the confirm
 panel has been dismissed. Pass YES if the user chose OK, NO if the user
 chose Cancel.
 @discussion For user security, your app should call attention to the fact
 that a specific website controls the content in this panel. A simple forumla
 for identifying the controlling website is frame.request.URL.host.
 The panel should have two buttons, such as OK and Cancel.
 
 If you do not implement this method, the web view will behave as if the user selected the Cancel button.
 */
// 弹出确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Confirm" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }]];
    
    [self presentViewController:alert animated:YES completion:nil];
}

/*! @abstract Displays a JavaScript text input panel.
 @param webView The web view invoking the delegate method.
 @param message The message to display.
 @param defaultText The initial text to display in the text entry field.
 @param frame Information about the frame whose JavaScript initiated this call.
 @param completionHandler The completion handler to call after the text
 input panel has been dismissed. Pass the entered text if the user chose
 OK, otherwise nil.
 @discussion For user security, your app should call attention to the fact
 that a specific website controls the content in this panel. A simple forumla
 for identifying the controlling website is frame.request.URL.host.
 The panel should have two buttons, such as OK and Cancel, and a field in
 which to enter text.
 
 If you do not implement this method, the web view will behave as if the user selected the Cancel button.
 */
// 弹出输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

#if TARGET_OS_IPHONE

/*! @abstract Allows your app to determine whether or not the given element should show a preview.
 @param webView The web view invoking the delegate method.
 @param elementInfo The elementInfo for the element the user has started touching.
 @discussion To disable previews entirely for the given element, return NO. Returning NO will prevent
 webView:previewingViewControllerForElement:defaultActions: and webView:commitPreviewingViewController:
 from being invoked.
 
 This method will only be invoked for elements that have default preview in WebKit, which is
 limited to links. In the future, it could be invoked for additional elements.
 */
// 是否预览
- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
    return YES;
}

/*! @abstract Allows your app to provide a custom view controller to show when the given element is peeked.
 @param webView The web view invoking the delegate method.
 @param elementInfo The elementInfo for the element the user is peeking.
 @param defaultActions An array of the actions that WebKit would use as previewActionItems for this element by
 default. These actions would be used if allowsLinkPreview is YES but these delegate methods have not been
 implemented, or if this delegate method returns nil.
 @discussion Returning a view controller will result in that view controller being displayed as a peek preview.
 To use the defaultActions, your app is responsible for returning whichever of those actions it wants in your
 view controller's implementation of -previewActionItems.
 
 Returning nil will result in WebKit's default preview behavior. webView:commitPreviewingViewController: will only be invoked
 if a non-nil view controller was returned.
 */
//- (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id <WKPreviewActionItem>> *)previewActions API_AVAILABLE(ios(10.0))

/*! @abstract Allows your app to pop to the view controller it created.
 @param webView The web view invoking the delegate method.
 @param previewingViewController The view controller that is being popped.
 */
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController {
    NSLog(@">>>>>>>>>> %s", __FUNCTION__);
}

#endif // TARGET_OS_IPHONE

#if !TARGET_OS_IPHONE

/*! @abstract Displays a file upload panel.
 @param webView The web view invoking the delegate method.
 @param parameters Parameters describing the file upload control.
 @param frame Information about the frame whose file upload control initiated this call.
 @param completionHandler The completion handler to call after open panel has been dismissed. Pass the selected URLs if the user chose OK, otherwise nil.
 
 If you do not implement this method, the web view will behave as if the user selected the Cancel button.
 */
//- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler API_AVAILABLE(macosx(10.12));

#endif

关键词

  1. 相比UIWebView的控制粒度更细。在服务端请求响应之后,会询问是否载入内容到当前WKWebView。而UIWebView是直接载入。URL的content下载完成,会询问是否允许下载的内容载入。
  2. 多了一个重定向的通知,WKWebView进程退出时有个terminate的回调方法。
  3. 不占用App自身内存。但web content process的内存带来了白屏等问题。
  4. alert、conform等视图需要通过WKUIDelegate协议来接收通知,iOS原生执行。
  5. Native->JS通信,WKWebView是异步的,而UIWebView是同步的。
  6. cookie机制比较复杂。

支持KVO的一些属性:

  1. title
  2. URL
  3. estimatedProgress
  4. hasOnlySecureContent,页面中的所有资源是否都是通过安全链接加载的?
  5. loading

其中,title、URL、loading通常可以用来检测是否加载成功,例如是否出现webView白屏等。

如何调试WebView

iOS模拟器进入对应的WebView界面,打开Safari,打开开发菜单即可。注意,调试时候,App不需要处于debugging中。

iOS真机调试WebView,则要先 设置->Safari浏览器->高级->打开JavaScript和Web检查器 , 同时使用Debug证书。

WKHTTPCookieStore

管理与特定的WKWebsiteDataStore关联的HTTP cookie的对象:

- (void)getAllCookies:(void (^)(NSArray<NSHTTPCookie *> *))completionHandler;
- (void)setCookie:(NSHTTPCookie *)cookie completionHandler:(void (^)(void))completionHandler;
- (void)deleteCookie:(NSHTTPCookie *)cookie completionHandler:(void (^)(void))completionHandler;
- (void)addObserver:(id<WKHTTPCookieStoreObserver>)observer;
- (void)removeObserver:(id<WKHTTPCookieStoreObserver>)observer;
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore;

注意,cookie可以用来监控。

JavaScriptCore

JSCore跟Google的V8引擎类似,都是用于解释执行JavaScript代码的核心引擎。

JSVirtualMachine,JSContext,JSValue,JSExport,evaluateJavaScript。

以下都是一对多:

JS VM ---> JSContext ---> JSValue

JSVirtualMachine

为JS代码的运行提供一个虚拟机环境。一个JS VM只能执行一个线程,若要多线程,则需要创建多个JS VM。跟JS的单线程模型是什么关系?

每个JS VM都有自己的GC,多个JS VM之间的对象无法传递。

JSContext

是JS运行环境的上下文,负责Native和JS的数据传递

JSValue

JS的值对象,提供JS的原始值对象与Native对象的转换方式。

Native->JS

[webView evaluateJavaScript:jsString]

或者

[jsContext evaluateScript:jsString];

使用toString,toNumber,toDictionary从JSValue中取出对应的Native对象。

若要使用JS的函数对象,则使用callWithArguments方法:

[context evaluateString:@"function addition(x,y) {return x+ y}"];
JSValue *addition = context[@"addition"];
JSValue *resultValue = [addition callWithArguments:@[@(4), @(8)]];
[resultValue toNumber];

Weex中类似:

- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args {
    return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}

JS->Native

使用block:

context[@"subtraction"] = ^(int x, int y) {
    return x + y;
}
JSValue *subValue = [context evaluateScript:@"subtraction(4,8);"];
[subValue toNumber];

JS调用Native代码,是通过block或closure来实现的。

JSExport

JSExport协议,可以将Native对象export到JSContext中。

自定义一个protocol继承JSExport协议,将OC对象传入JS中。

@protocol PersonJSExport <JSExport>

@end

jsContext[@"Person"] = [Person class];
[jsContext evaluateScript:@"var personWithName = function(name) { var person = Person.persionWithName(name); return person; }"];
[[jsContext objectForKeyedSubscript:@"persionWithName"] callWithArguments:@[@"Chris"]];

这里,将OC的类Person继承JSExport协议,然后封装一个函数personWithName来调用该OC类的初始化代码。 通过objectForKeyedSubscript来取出JS中的函数,使用callWithArguments:来执行。

JSPatch原理

除了block和JSExport之外,还可以使用JSContext和JSValue的类型转换和OC的runtime消息转发机制实现动态调用,巧妙避开JSExport协议,即JSPatch的实现原理。

JavaScriptCore的组成

真实的JavaScript虚拟机,包含了解释器和运行时。

解释器将高级的脚本语言编译成字节码,runtime用来管理runtime的内存空间。

JSCore内部由Parse,Interpreter,Compiler,GC组成。

解释执行

由Parser进行词法分析,语法分析,生成字节码。

Interpreter进行解释执行。先有LLInt(Low Level Interpreter)来执行Parser生成的字节码,JSCore会对运行频次高的函数或循环进行优化。

优化器有Baseline JIT,DFG JIT,FTL JIT等。

执行js代码

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *error))completionHandler;

注意,其completionHandler是在主线程执行的。

其他关键点

WKProcessPool

Web Content的进程池。WKWebView对象初始化的时候,会从该进程池中获取一个Web Content进程对象。

截图

- (void)takeSnapshotWithConfiguration:(WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage *snapshotImage, NSError *error))completionHandler;

可使用WKSnapshotConfiguration来自定义webView截图区域。

WKUserContentController

提供了向WKWebView发送JS消息或者注入JS脚本的方法。

一个WKUserScript对象代表了一个可以被注入网页中的脚本。脚本注入的时机,只能通过WKUserScriptInjectionTime枚举来设置。其包含WKUserScriptInjectionTimeAtDocumentStart和WKUserScriptInjectionTimeAtDocumentEnd。

WKScriptMessageHandler

实现该协议的类,可以接收来自网页的JS调用方法。

WKScriptMessage对象即包含了一个JS消息的相关信息。其属性id body只能是对象类型。

WKWebsiteDataStore

如果一个WebView关联了一个非持久化的WKWebsiteDataStore,将不会有数据被写入到文件系统 该特性可以用来实现隐私浏览。 同样可以通过设置configuration来设置无痕模式。

一个WKWebsiteDataStore对象代表了被网页使用的各种类型的数据。包括cookies,磁盘文件,内存缓存以及持久化数据如WebSQL,IndexedDB数据库,local storage。

WKURLSchemeHandler

用来处理WebKit无法处理的URL Scheme类型的资源

加载特定资源的时候使用。

- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask;
- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask;

WKURLSchemeTask即用来加载资源的任务。

WKWebView的常见套路

使用KVO来监控load进度

[webView addObserver:self forKeyPath:@"loading" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"loading"]) {
        NSLog(@"loading: %d", _webView.isLoading);
    } else if ([keyPath isEqualToString:@"estimatedProgress"]) {
        NSLog(@"estimatedProgress: %f", _webView.estimatedProgress);
        self.progressView.progress = _webView.estimatedProgress;
        if (self.progressView.progress >= 1.f) {
            self.progressView.progress = 0.f;
        }
    }
}

查看所有cookie

webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
    for cookie in cookies {
        if cookie.name == "authentication" {
            self.webView.configuration.websiteDataStore.httpCookieStore.delete(cookie)
        } else {
            print("\(cookie.name) is set to \(cookie.value)")
        }
    }
}

白屏原因及处理措施

详细内容见之前一篇博客 WKWebView的一些问题汇总

参考资料