背景
新一期的需求中,App端会调用一个H5,由于这个H5页面是会做登录态的判断,因此当用户在App端已经登录时,需要同步登陆状态到H5页面,解决方案是native调用H5页面的时候将登录态写入cookie中供H5页面使用。
问题
UIWebView中可以调用NSHTTPCookieStorage单例对象写入cookie,但是WKWebView无法像UIWebView一样优雅的读取和写入cookie。
虽然苹果在iOS 11中提供了WKWebView操作cookie对象,但考虑到兼容性问题,仍然使用了能兼容所有iOS版本的写入cookie的方法。
解决方案
将cookie通过JS注入到WKWebView中
基本思路
1、将需要的cookie拼接为JS字符串,如:
document.cookie = 'userToken=ks339msjlg;secure=true';document.cookie = 'userToken=ks339msjlg;secure=true';
2、在页面加载前将jS串通过addUserScript方法加入到WKWebView的WKUserContentController中。
3、加载指定的H5页面。
优化后思路
上面的思路已经可以解决写入cookie的问题,但直接在代码中这样实现显得不够高大上,接下来再从代码设计角度做些优化。优化后的思路为:
1、创建两个WKWebView, 一个加载H5页面使用,一个专门加载cookie使用。
2、而让两个webview可以使用相同的cookie空间的关键点就是使用同一个processPool,这样就可以让同步cookie的逻辑和加载webview的逻辑解耦,代码清晰、可读性后果,避免被后人骂。
代码片段
1、创建ProcessPool
+ (WKProcessPool *)sharedProcessPool
{
static WKProcessPool *processPool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
processPool = [WKProcessPool new];
});
return processPool;
}
2、创建两个WKWebView
- (WKWebView *)webview {
if (!_webview) {
WKUserContentController *userContentController = WKUserContentController.new;
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
//processPool需要和_cookieWebview的公用一个才能共享_cookieWebview的cookie
webViewConfig.processPool = [EDWKWebViewController sharedProcessPool];
_webview = [[WKWebView alloc] initWithFrame:CGRectMake(0, NavigationBarHeight, Screen_Width, Screen_Height-NavigationBarHeight) configuration:webViewConfig];
_webview.UIDelegate = self;
_webview.navigationDelegate = self;
}
return _webview;
}
- (WKWebView *)cookieWebview {
if (!_cookieWebview) {
WKUserContentController *userContentController = WKUserContentController.new;
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
webViewConfig.processPool = [EDWKWebViewController sharedProcessPool];
_cookieWebview = [[WKWebView alloc] initWithFrame:CGRectZero configuration:webViewConfig];
_cookieWebview.UIDelegate = self;
_cookieWebview.navigationDelegate = self;
}
return _cookieWebview;
}
3、将cookie的生成方式单独放在一个类中,以便统一管理
@implementation EDWebviewCookieManager
+ (instancetype)sharedCookieManager {
static EDWebviewCookieManager *sharedCookieManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!sharedCookieManager) {
sharedCookieManager = [[self alloc] init];
}
});
return sharedCookieManager;
}
- (void)shouldLoadRequestURL:(NSURL *)url scriptCallback:(void (^)(NSString *))scriptCallback {
if (!scriptCallback) {
return;
}
//此处可根据url决定是否需要加载cookie等逻辑
if (!url.host.length) {
scriptCallback(nil);
}
//静态cookie串或者从接口中获取拼接
NSDictionary *properties = @{
@"id":@"2018",
@"name":@"ella"
};
NSMutableString *scriptString = [NSMutableString string];
for (NSString *key in properties.allKeys) {
NSString *cookieString = [NSString stringWithFormat:@"%@=%@;", key, properties[key]];
[scriptString appendString:[NSString stringWithFormat:@"document.cookie = '%@';", cookieString]];
}
scriptCallback([scriptString copy]);
}
- (void)removeCookieWithURL:(NSURL *)url {
if (@available(iOS 9.0, *)) {
NSSet *cookieTypeSet = [NSSet setWithObject:WKWebsiteDataTypeCookies];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:cookieTypeSet modifiedSince:[NSDate dateWithTimeIntervalSince1970:0] completionHandler:^{
}];
}
}
@end
4、加载cookie和H5页面
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"EDWKWebViewController";
[self.view addSubview:self.webview];
[self startToLoadRequest];
}
- (void)startToLoadRequest {
NSURL *url = [NSURL URLWithString:self.rootUrl];
__weak typeof(self) weakSelf = self;
self.loadAction = ^{
__strong typeof(self) strongSelf = weakSelf;
[strongSelf.webview loadRequest:[NSURLRequest requestWithURL:url]];
};
[self syncCookieForURL:url loadAction:self.loadAction];
}
- (void)syncCookieForURL:(NSURL *)url loadAction:(EDLoadRequestAction)loadAction {
[[EDWebviewCookieManager sharedCookieManager] 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)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
if (webView == self.cookieWebview) {
//cookieWebview加载成功后开始加载真正的url
if (self.loadAction) {
self.loadAction();
}
return;
}
}
总结
遇到技术问题,先理清解决问题的思路,一切就变得简单,所以尽量多花时间在理清思路上。
点这里下载Demo。如有问题,可以留言或者私信。