在iOS 8.0以后苹果推出WKWebView,之前有性能问题的UIWebView基本就被弃用了,这里整理下我的WKWebView之旅和怎么封装的。
1、WKWebView有个绕不过去的问题就是Cookie.
我们先来看下Cookie到底是个什么东西:
简单地说,cookie 就是浏览器储存在用户电脑上的一小段文本文件。cookie 是纯文本格式,不包含任何可执行的代码。一个 Web 页面或服务器告知浏览器按照一定规范来储存这些信息,并在随后的请求中将这些信息发送至服务器,Web 服务器就可以使用这些信息来识别不同的用户。大多数需要登录的网站在用户验证成功之后都会设置一个 cookie,只要这个 cookie 存在并可以,用户就可以自由浏览这个网站的任意页面。再次说明,cookie 只包含数据,就其本身而言并不有害。紧跟 cookie 值后面的每个选项都以分号和空格分开,每个选择都指定了 cookie 在什么情况下应该被发送至服务器。第一个选项是过期时间(expires),指定了 cookie 何时不会再被发送至服务器,随后浏览器将删除该 cookie。该选项的值是一个 Wdy, DD-Mon-YYYY HH:MM:SS GMT 日期格式的值。下一个选项是 domain,指定了 cookie 将要被发送至哪个或哪些域中。默认情况下,domain会被设置为创建该 cookie 的页面所在的域名,所以当给相同域名发送请求时该 cookie 会被发送至服务器。另一个控制 Cookie 消息头发送时机的选项是 path 选项,和 domain 选项类似,path选项指定了请求的资源 URL 中必须存在指定的路径时,才会发送Cookie 消息头。这个比较通常是将 path 选项的值与请求的 URL 从头开始逐字符比较完成的。如果字符匹配,则发送 Cookie 消息头。最后一个选项是 secure。不像其它选项,该选项只是一个标记而没有值。只有当一个请求通过 SSL 或 HTTPS 创建时,包含 secure 选项的 cookie 才能被发送至服务器。这种 cookie 的内容具有很高的价值,如果以纯文本形式传递很有可能被篡改。
UIWebView Cookie
同一个应用,不同UIWebView之间的Cookie是自动同步的。并且可以被其他网络类访问比如NSURLConnection,AFNetworking。
它们都是保存在NSHTTPCookieStorage容器中。 当UIWebView加载一个URL的时候,在加载完成时候,Http Response,对Cookie进行写入,更新或者删除,结果更新Cookie到NSHTTPCookieStorage存储容器中。
WKWebView Cookie
NSURLCache和NSHTTPCookieStroage无法操作(WKWebView)WebCore进程的缓存和Cookie。
WKWebView实例将会忽略任何的默认网络存储器(NSURLCache, NSHTTPCookieStorage, NSCredentialStorage) 和一些标准的自定义网络请求类(NSURLProtocol,等等.)。
WKWebView实例不会把Cookie存入到App标准的的Cookie容器(NSHTTPCookieStorage)中,因为 NSURLSession/NSURLConnection等网络请求使用NSHTTPCookieStorage进行访问Cookie,所以不能访问WKWebView的Cookie,现象就是WKWebView存了Cookie,其他的网络类如NSURLSession/NSURLConnection却看不到。,
与Cookie相同的情况就是WKWebView的缓存,凭据等。WKWebView都拥有自己的私有存储,因此和标准Cocoa网络类兼容的不是那么好。
你也不能自定义requests(增加自己的http header,更改已经存在的header)使用自定义的 URL schemes等等,因为NSURLProtocol也是不支持WKWebView的。
WKWebView Cookie 写入
1、JS注入:
WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc]
initWithSource:[NSString stringWithFormat:@"document.cookie = '%@'", [self setCurrentCookie]]
injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript];
- (NSString *)setCurrentCookie {
return @"";
}
2、NSMutableURLRequest 注入
- (void)loadURLRequest {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url];
[request addValue:[self readCurrentCookie] forHTTPHeaderField:@"Cookie"];
[self.webView loadRequest:request];
}
- (NSString *)setCurrentCookie {
return @"";
}
划重点:坑一:JS注入的Cookie,比如PHP代码在Cookie容器中取是取不到的, javascript document.cookie能读取到,浏览器中也能看到。
NSMutableURLRequest 注入的PHP等动态语言直接能从$_COOKIE对象中获取到,但是js读取不到,浏览器也看不到
所以合理的办法让js,php,浏览器都能读取到相同的Cookie方法就是创建WebView的时候javascript注入Cookie,一开始发送NSMutableURLRequest请求的时候也要加上Cookie,并且保证两个地方的设置的cookie一致。
坑二:WKWebView的cookie需要设置domain和path默认情况下会带进去不是通用的。(今天刚发现的o)
坑三:网页登录跳原生之后登录成功后dimis后你需要重新注入和刷新把cookie塞进去,所以你的viewWillAppear每次都需要重新加载WKWebView和重新loadRequest,简直了。。。
KZWWebViewController 是如何做的呢?
我们知道,app里经常跳各种网页,我们不可能每个网页都去单独处理,所以我们写一个通用的可配置的KZWWebViewController,只需要传url进来就可以,其他你不要管了,是不是完美。
所以我们需要来设计一个这样的KZWWebViewController,首先必须的是跳转的url和其他的配置参数,然后是接入jsbridge,方便我们和网页的换下调用,这样我们的这个KZWWebViewController就基本满足需求了。
所以我的做法是抽出一个类来管理,它叫KZWRouterHelper,暴露一个方法
+ (void)pushbyPath:(NSString *)path xxx(xxx)xx .....
里面的操作是把你需要的配置的加上,然后转成字典塞入router
NSDictionary *params = @{
@"path": [path kzw_urlEncode],
@"timestimp":[NSString stringWithFormat:@"%g", [[NSDate date] timeIntervalSince1970]]
};
NSString *url =
[NSString stringWithFormat:@"%@?%@", KZWWebViewControllerRouterPath, [NSURL elm_queryStringFromParameters:params]];
return [[ELMRouter sharedRouter] open:url animated:NO showStyle:ELMPageShowStylePush];
param里包含了你所以的配置,类如:
NSDictionary *params = @{
@"path": path,
@"fullScreen": @(NO),
@"fullUrl": @(NO),
@"title": string?string:@"",
@"timestimp":[NSString stringWithFormat:@"%g", [[NSDate date] timeIntervalSince1970]]
};
这个根据业务需求来配置就好,然后在controller里根据不同的参数做相应的处理就可以了,这样你的整个项目里所有的网页跳转就一行代码就好了:
[KZWRouterHelper pushbyURL:@"xxxx" ];
接入的jsbirdge最好是选择jscore的方式的,这样是同步,网页也可以加个配置方法,这个的主要目的,有的网页需要由网页自己来控制一些显示,原理同上我们自己的配置都是根据参数做不同的处理,具体看KZWWebViewController,然后很多时候产品想要跳二级页面的时候可以有2个返回,这时候如果你的leftBarButtonItems是统配的话最好在页面加载的时候就先设置一个返回,然后在didFinishNavigation代理设置成2个返回,一个返回上一个网页一个返回我们上一个控制器。这样做主要是你开始设置成统配后来直接配2个会闪一下。
还有就是WKWebView中的进度条,WKWebView的进度条比较简单你只要写一个UIProgressView然后监听WKWebView的加载进度就好了,然后title的显示也是监听就好了,如果没取到记得设置一个默认的。
[self.webView addObserver:self
forKeyPath:NSStringFromSelector(@selector(estimatedProgress))
options:0
context:nil];
[self.webView addObserver:self forKeyPath:NSStringFromSelector(@selector(title)) options:NSKeyValueObservingOptionNew context:NULL];
然后是适配iPhone X的记得加这行代码:
if (KZW_iPhoneX) {
self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
最后记得释放你的监听:
- (NSString *)fullString:(NSString *)path {
NSString *domain = nil;
switch ([ELMEnvironmentManager environment]) {
case ELMEnvBeta:
domain = @"xxxxx";
break;
case ELMEnvAlpha:
domain = @"xxxxx";
break;
case ELMEnvProduction:
domain = @"xxxxx";
break;
default:
domain = @"xxxxx";
break;
}
if ([path containsString:@"http"]) {
return path;
}else {
return [domain stringByAppendingString:path];
}
}
- (void)dealloc {
self.webView.UIDelegate = nil;
[self.webView stopLoading];
[self.webView removeObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
[self.webView removeObserver:self forKeyPath:NSStringFromSelector(@selector(title))];
self.webView = nil;
}
组url的时候可以加个环境的配置。
就完啦,希望对你有用。具体看KZWFoudation中的KZWWebViewController,KZWRouterHelper和KZWDSJavaScripInterface。https://github.com/ouyrp/KZWFoundation
哦还有一个cookie的清空和网页清缓存我也加上吧,前2篇关于WKWebView的文章就删了。
- (NSString *)readCurrentCookie {
return @"";
}
- (NSString *)setCurrentCookie {
return @"";
}
cookie清空只要这个2个方法里面参数清了就好了
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
[dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
for (WKWebsiteDataRecord *record in records)
{
if ( [record.displayName containsString:@"xxxxx"])
{
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
forDataRecords:@[record]
completionHandler:^{
NSLog(@"Cookies for %@ deleted successfully",record.displayName);
}];
}
}
}];
}else {
NSString *librarypath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
NSString *cookiesFolderPath = [librarypath stringByAppendingString:@"/Cookies"];
[[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:nil];
}
NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [cookieJar cookies]) {
[cookieJar deleteCookie:cookie];
}
这是清缓存,亲测有效
之后还是会出KZWFoudation的系列文章,写下自己的封装思路。