每个 WKWebView 都有一个私有的 Cookie 存储,不会与标准的 Cookie 容器 NSHTTPCookieStorage 共享。WKWebView cookie 问题就在于 WKWebView 发起请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。

而 UIWebView 则没有这个问题,UIWebView 发起请求会自动带上 NSHTTPCookieStorage 中的 Cookie。 UIWebView 没有私有的 Cookie 存储,APP 中所有的 UIWebView 共享同一个NSHTTPCookieStorage 容器中的 Cookie 信息。

解决方案

iOS 11 之前的系统

  1. WKWebView loadRequest 前,在 request header 中设置 Cookie,解决首个请求 Cookie 带不上的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://test.com/"]];
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSArray *cookies = cookieStorage.cookies;
    NSMutableString *cookieString = [[NSMutableString alloc] init];
    for (NSHTTPCookie *cookie in cookies) {
    [cookieString appendFormat:@"document.cookie = '%@=%@;path=/';",cookie.name,cookie.value];
    }
    [request setValue:cookieString forHTTPHeaderField:@"Cookie"];
    [webView loadRequest:request];
  2. 通过 document.cookie 设置 Cookie,解决后续请求 Cookie 问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSArray *cookies = cookieStorage.cookies;
    NSMutableString *cookieString = [[NSMutableString alloc] init];
    for (NSHTTPCookie *cookie in cookies) {
    [cookieString appendFormat:@"document.cookie = '%@=%@;path=/';",cookie.name,cookie.value];
    }
    WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource: cookieString injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    WKUserContentController *userContentController = [WKUserContentController new];
    [userContentController addUserScript:cookieScript];

注:document.cookie() 无法跨域设置 cookie

iOS 11 及之后的系统

iOS 11 及之后的系统可通过 WKHTTPCookieStore 来管理 HTTP Cookie 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (@available(iOS 11.0, *)) {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookies = cookieStorage.cookies;
WKWebsiteDataStore *dataStore = [WKWebsiteDataStore nonPersistentDataStore];
NSInteger count = cookies.count;
for (NSInteger i = 0; i < count; i++) {
NSHTTPCookie *cookie = cookies[i];
[dataStore.httpCookieStore setCookie:cookie completionHandler:nil];
}
config.websiteDataStore = dataStore;
WKWebView *webView = [[WKWebView alloc] initWithFrame:frame
configuration:config];
}

注:

WKWebsiteDataStore 要用 nonPersistentDataStore 新建一个,使用 defaultDataStore 创建设置的 Cookie 信息不会生效。

WKWebViewConfiguration 要在 WKWebView 创建的时候创建,WKWebView 创建后再更改 WKWebViewConfiguration 配置信息不会生效。

WKProcessPool

A WKProcessPool object represents a pool of Web Content processes.

苹果开发者文档对 WKProcessPool 的定义如上,WKProcessPool 代表 web 内容的进程池。WKWebView 初始化时会从 WKWebViewConfiguration 中指定的 WKProcessPool 进程池中创建一个新的 web 内容进程,或者使用该进程池中一个已存在的进程。

多个 WKWebView 之间不会共享 Cookie 信息,如上所述,可通过让多个 WKWebView 共享同一个 WKProcessPool 实例,来实现多个 WKWebView 之间共享 Cookie 信息。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// WKProcessPool+Shared.h

#import <WebKit/WebKit.h>

@interface WKProcessPool (Shared)

+ (WKProcessPool *)sharedWKProcessPool;

@end

// WKProcessPool+Shared.m

#import "WKProcessPool+Shared.h"

@implementation WKProcessPool (Shared)

+ (WKProcessPool *)sharedWKProcessPool {
static WKProcessPool *pool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pool = [[WKProcessPool alloc] init];
});
return pool;
}

@end

// 配置 WKWebView

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.processPool = [WKProcessPool sharedWKProcessPool];
WKWebView *webView = [[WKWebView alloc] initWithFrame:frame
configuration:config];

WKWebView 白屏问题

UIWebView 当内容占用太大的时候,App 会 crash。而在 WKWebView 上当内容占用太大的时候,App 不会 crash,WebContent process 会 crash,从而出现白屏的现象,这个时候 webView.URL 会变为 nil。

iOS 9以后 WKNavigationDelegate 中新增了一个方法:

1
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));

WKWebView 页面即将白屏的时候,系统会调用上面的方法,此时 webView.URL 还有值不是 nil,所以可以通过简单的 [webView reload] 来解决白屏问题。