WKWebView的cookie共享问题:与native之间、多
这是两个不同的项目的总结:
项目一,只需多个webView之间共享cookie
项目二,在项目一的基础上,增加了与native之间cookie的共享问题。
没有耐心的同学,可以直接到文章末尾查看。
项目二中,共享cookie时,我最初没有注意到cookie去重的问题,导致加载时,始终提示未登录,仔细查找了问题,才发现是cookie重复,并且最后一个cookie值为undefined导致的。
因项目一需求,需要在app中,初次加载首页的webView时,先行做webView的登录,因为由首页跳转新的webView,打开新页面是需要网页上已经登录方可。
在使用了WKWebView后,这已经是我第三次在尝试处理这个问题了,各种尝试。
1.在loadRequest时,添加cookie信息
self.webRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
NSData *cookiesdata = [[NSUserDefaults standardUserDefaults] objectForKey:HHUserDefaultCookie];
if([cookiesdata length]) {
NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData:cookiesdata];
NSHTTPCookie *cookie;
for (cookie in cookies) {
NSString *cookieStr = [NSString stringWithFormat:@"%@=%@",cookie.name,cookie.value];
[self.webRequest addValue:cookieStr forHTTPHeaderField:@"Cookie"];
}
HHLog(@"请求时需要的Cookie %@",cookies);
}
[self.webView loadRequest:self.webRequest];
失败!
2.为webView的configuration设置cookie
HHWeakSelf(weakSlef);
WKUserContentController *userCC = self.webView.configuration.userContentController;
HHWKJSOCHandle *weakHandle = [[HHWKJSOCHandle alloc]initWithDelegate:weakSlef isActivity:NO];
[userCC addScriptMessageHandler:weakHandle name:HHNativeCategorymoreMethod];
[userCC addScriptMessageHandler:weakHandle name:HHNaviveCategoryMethod];
[userCC addScriptMessageHandler:weakHandle name:HHNativeProductMethod];
[userCC addScriptMessageHandler:weakHandle name:HHNativeActivityMethod];
// [userCC addScriptMessageHandler:weakSelf name:HHNativeShareMethod];
NSData *cookiesdata = [[NSUserDefaults standardUserDefaults] objectForKey:HHUserDefaultCookie];
NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData:cookiesdata];
NSHTTPCookie *cookie;
for (cookie in cookies) {
NSString *cookieStr = [NSString stringWithFormat:@"document.cookie = '%@=%@';",cookie.name,cookie.value];
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: cookieStr injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userCC addUserScript:cookieScript];
}
失败!
3.网页加载完成后,设置cookie
在- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation方法中
//取出cookie
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
//js函数
NSString *JSFuncString =
@"function setCookie(name,value,expires)\
{\
var oDate=new Date();\
oDate.setDate(oDate.getDate()+expires);\
document.cookie=name+'='+value+';expires='+oDate+';path=/'\
}\
function getCookie(name)\
{\
var arr = document.cookie.match(new RegExp('(^| )'+name+'=({FNXX==XXFN}*)(;|$)'));\
if(arr != null) return unescape(arr[2]); return null;\
}\
function delCookie(name)\
{\
var exp = new Date();\
exp.setTime(exp.getTime() - 1);\
var cval=getCookie(name);\
if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\
}";
//拼凑js字符串
NSMutableString *JSCookieString = JSFuncString.mutableCopy;
for (NSHTTPCookie *cookie in cookieStorage.cookies) {
NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
[JSCookieString appendString:excuteJSString];
}
HHLog(@"JS字符串Cookie: %@",JSCookieString);
//执行js
[webView evaluateJavaScript:JSCookieString completionHandler:^(id obj, NSError * _Nullable error) {
NSLog(@"执行js %@",error);
}];
失败!
包括这三种方式全部设置上,依然失败。
静下心来思考,确定出我的问题并不是让WKWebView和Native共享cookie,而是多个WKWebView之间如何共享cookie。
问题根源确定了,下一步就简单多了,只需要让多个WKWebView共用同一个WKProcessPool实例就可以。
那么,如何能满足WKWebView和Native共享cookie的同时,并且使得多个WKWebView共用同一个WKProcessPool实例。
很简单,只要两者相结合就好。
基于这个思路,我们是不是可以先行让webView加载一个网址,在加载之初就先行为webView的configuration设置cookie,如此,当第一个网址加载完毕之后,webView之间就可以共享cookie了呢。
基于这个设想,来实现。
使用示例,在WKWebViewontroller中
- (WKWebView *)webView{
if (_webView == nil) {
WKUserContentController *userCC = [WKUserContentController new];
[userCC addScriptMessageHandler:self name:@"urlDirect"];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
//设置HTML5视频是否允许网页播放 设置为NO则会使用本地播放器
config.allowsInlineMediaPlayback = YES;
config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
config.userContentController = userCC;
config.preferences.javaScriptEnabled = YES;
// config.suppressesIncrementalRendering = YES;
config.processPool = [HHWKCookieSyncManager sharedCookieManager].processPool;
_webView = [[WKWebView alloc]initWithFrame:ScreenBounds configuration:config];
_webView.backgroundColor = [UIColor whiteColor];
_webView.navigationDelegate = self;
_webView.UIDelegate = self;
_webView.scrollView.delegate = self;
[_webView sizeToFit];
if (@available(iOS 11.0, *)) {
_webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self.view addSubview:_webView];
[_webView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(UIEdgeInsetsMake(0, 0, QSJSafeAreaBottomHeight, 0));
}];
}
return _webView;
}
注意关键代码
config.processPool = [HHWKCookieSyncManager sharedCookieManager].processPool;
加载网络请求时
- (void)refreshData{
if (self.errView) {
[self.errView removeFromSuperview];
}
//设置请求头
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
if ([AccountManager shareInstance].isLogin) {
[request setValue:[QSJUserDefaults objectForKey:QSJUserID] forHTTPHeaderField:@"userId"];
[request setValue:[QSJUserDefaults objectForKey:QSJUserToken] forHTTPHeaderField:@"token"];
}
request.timeoutInterval = 10;
HHWKCookieSyncManager *cookieManager = [HHWKCookieSyncManager sharedCookieManager];
//这行代码可以注释
[cookieManager setWebViewCookie:self.webView];
if (@available(iOS 11.0, *)) {
[self.webView loadRequest:request];
}else{
@axc_weakify_self;
cookieManager.loadAction = ^{
@axc_strongify_self;
[self.webView loadRequest:request];
};
cookieManager.loadFailure = ^(NSString *errMsg) {
@axc_strongify_self;
[self createErrViewWithMsg:errMsg];
};
[cookieManager syncCookieForURL:[NSURL URLWithString:self.url] loadAction:cookieManager.loadAction];
}
}
下面是主要文件代码
HHWKCookieSyncManager.h文件
//
// HHWKCookieSyncManager.h
// GlobalTimes
//
// Created by apple on 2017/4/11.
// Copyright © 2017年 Hannah. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface HHWKCookieSyncManager : NSObject
@property (nonatomic, strong) WKProcessPool *processPool;
@property (nonatomic, strong) WKWebView *cookieWebview;
@property (nonatomic, copy) void(^loadAction)(void);
@property (nonatomic, copy) void(^loadFailure)(NSString *errMsg);
+ (instancetype)sharedCookieManager;
+ (NSString *)getCookieStr;
- (void)setWebViewCookie:(WKWebView *)webView;
- (void)syncCookieForURL:(NSURL *)url loadAction:(void(^)(void))loadAction;
- (void)shouldLoadRequestURL:(NSURL *)url scriptCallback:(void (^)(NSString *))scriptCallback ;
+ (void)removeCookieWithURL:(NSURL *)url;
@end
HHWKCookieSyncManager.m文件
//
// HHWKCookieSyncManager.m
// GlobalTimes
//
// Created by apple on 2017/4/11.
// Copyright © 2017年 Hannah. All rights reserved.
//
#import "HHWKCookieSyncManager.h"
@interface HHWKCookieSyncManager ()<WKNavigationDelegate,WKUIDelegate>
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) NSURL *testUrl;
@end
@implementation HHWKCookieSyncManager
static inline WKUserScript * WKCookieUserScript(NSString *cookieString) {
if (!cookieString.length) {
return nil;
}
WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:cookieString
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:NO];
return cookieScript;
}
+ (instancetype)sharedCookieManager{
static HHWKCookieSyncManager *__manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__manager = [[self alloc] init];
});
return __manager;
}
- (WKProcessPool *)processPool{
if (_processPool == nil) {
static dispatch_once_t onceToken;
static WKProcessPool *processPool;
dispatch_once(&onceToken, ^{
processPool = [[WKProcessPool alloc] init];
});
_processPool = processPool;
}
return _processPool;
}
- (WKWebView *)cookieWebview {
if (!_cookieWebview) {
WKUserContentController *userContentController = WKUserContentController.new;
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
webViewConfig.processPool = [HHWKCookieSyncManager sharedCookieManager].processPool;
_cookieWebview = [[WKWebView alloc] initWithFrame:CGRectZero configuration:webViewConfig];
_cookieWebview.UIDelegate = self;
_cookieWebview.navigationDelegate = self;
}
return _cookieWebview;
}
+ (NSString *)getCookieStr{
NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSMutableString *cookieString = [[NSMutableString alloc] init];
for (NSHTTPCookie *cookie in [cookieJar cookies]) {
//此处可进行cookie去重
if ([cookie.value isEqualToString:@"undefined"] == NO && cookie.value != NULL && cookie.value.length > 0) {
[cookieString appendFormat:@"document.cookie = '%@=%@';\n", cookie.name, cookie.value];
}
}
//删除最后一个“;”
// [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
return [cookieString copy];
}
- (void)setWebViewCookie:(WKWebView *)webView{
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookieStorage.cookies) {
if (@available(iOS 11.0, *)) {
[webView.configuration.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
} else {
// Fallback on earlier versions
NSString *cookieString = QSJFormat(@"document.cookie = '%@=%@';\n", cookie.name, cookie.value);
WKUserScript *script = [[WKUserScript alloc] initWithSource:cookieString injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[webView.configuration.userContentController addUserScript:script];
}
}
}
- (void)syncCookieForURL:(NSURL *)url loadAction:(void(^)(void))loadAction {
[self shouldLoadRequestURL:url scriptCallback:^(NSString *cookieScript) {
if (cookieScript.length) {
[self.cookieWebview.configuration.userContentController removeAllUserScripts];
[self.cookieWebview.configuration.userContentController addUserScript:WKCookieUserScript(cookieScript)];
NSString *baseWebUrl = [NSString stringWithFormat:@"%@://%@", url.scheme,url.host];
//如果需要加载cookie,则需要再cookie webview加载结束后再加载url,也就是在webView:(WKWebView *)webView didFinishNavigation方法中开始加载url
[self.cookieWebview loadHTMLString:@"" baseURL:[NSURL URLWithString:baseWebUrl]];
} else {
//如果没有cookie需要加载,则直接加载url
if (loadAction) {
loadAction();
}
}
}];
}
- (void)shouldLoadRequestURL:(NSURL *)url scriptCallback:(void (^)(NSString *))scriptCallback {
if (!scriptCallback) {
return;
}
//此处可根据url决定是否需要加载cookie等逻辑
if (!url.host.length || [url.host isEqualToString:QSJHost] == NO) {
scriptCallback(nil);
return;
}
scriptCallback([HHWKCookieSyncManager getCookieStr]);
}
+ (void)removeCookieWithURL:(NSURL *)url{
if (@available(iOS 9.0, *)) {
NSSet *cookieTypeSet = [NSSet setWithObject:WKWebsiteDataTypeCookies];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:cookieTypeSet modifiedSince:[NSDate dateWithTimeIntervalSince1970:0] completionHandler:^{
}];
}
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
[webView evaluateJavaScript:[HHWKCookieSyncManager getCookieStr] completionHandler:^(id _Nullable response, NSError * _Nullable error) {
}];
if (self.loadAction) {
self.loadAction();
}
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
if (error.code == NSURLErrorCancelled || error.domain == NSURLErrorDomain) {
return;
}
if (self.loadFailure) {
self.loadFailure(error.localizedDescription);
}
}
@end