KZWFoudation系列之WKWebView的封装

在iOS 8.0以后苹果推出WKWebView,之前有性能问题的UIWebView基本就被弃用了,这里整理下我的WKWebView之旅和怎么封装的。

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 = [[WKUserScriptalloc]

initWithSource:[NSStringstringWithFormat:@"document.cookie = '%@'", [selfsetCurrentCookie]]

injectionTime:WKUserScriptInjectionTimeAtDocumentStartforMainFrameOnly:NO];

[userContentController addUserScript:cookieScript];

- (NSString*)setCurrentCookie {

return@"";

}

2、NSMutableURLRequest注入

- (void)loadURLRequest {

NSMutableURLRequest*request = [NSMutableURLRequestrequestWithURL:self.url];

[request addValue:[selfreadCurrentCookie] 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默认情况下会带进去不是通用的。(今天刚发现的)

坑三:网页登录跳原生之后登录成功后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":[NSStringstringWithFormat:@"%g", [[NSDatedate] timeIntervalSince1970]]

};

NSString*url =

[NSStringstringWithFormat:@"%@?%@", KZWWebViewControllerRouterPath, [NSURLelm_queryStringFromParameters:params]];

return[[ELMRouter sharedRouter] open:url animated:NOshowStyle: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:

context:nil];

[self.webView addObserver:selfforKeyPath:NSStringFromSelector(@selector(title)) options:NSKeyValueObservingOptionNewcontext:NULL];

然后是适配iPhone X的记得加这行代码:

if(KZW_iPhoneX) {

self.webView.scrollView.contentInsetAdjustmentBehavior =UIScrollViewContentInsetAdjustmentNever;

}

最后记得释放你的监听:

- (NSString*)fullString:(NSString*)path {

NSString*domain =nil;

switch([ELMEnvironmentManager environment]) {

caseELMEnvBeta:

domain =@"xxxxx";

break;

caseELMEnvAlpha:

domain =@"xxxxx";

break;

caseELMEnvProduction:

domain =@"xxxxx";

break;

default:

domain =@"xxxxx";

break;

}

if([path containsString:@"http"]) {

returnpath;

}else{

return[domain stringByAppendingString:path];

}

}

- (void)dealloc {

self.webView.UIDelegate =nil;

[self.webView stopLoading];

[self.webView removeObserver:selfforKeyPath:NSStringFromSelector(@selector(estimatedProgress))];

[self.webView removeObserver:selfforKeyPath:NSStringFromSelector(@selector(title))];

self.webView =nil;

}

组url的时候可以加个环境的配置。

就完啦,希望对你有用。具体看KZWFoudation中的KZWWebViewController,KZWRouterHelper和KZWDSJavaScripInterface。

哦还有一个cookie的清空和网页清缓存我也加上吧,前2篇关于WKWebView的文章就删了。

- (NSString*)readCurrentCookie {

return@"";

}

- (NSString*)setCurrentCookie {

return@"";

}

cookie清空只要这个2个方法里面参数清了就好了

if([[UIDevicecurrentDevice].systemVersion floatValue] >=9.0) {

WKWebsiteDataStore*dateStore = [WKWebsiteDataStoredefaultDataStore];

[dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStoreallWebsiteDataTypes]

completionHandler:^(NSArray * __nonnullrecords) {

for(WKWebsiteDataRecord*recordinrecords)

{

if( [record.displayName containsString:@"xxxxx"])

{

[[WKWebsiteDataStoredefaultDataStore] 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"];

[[NSFileManagerdefaultManager] removeItemAtPath:cookiesFolderPath error:nil];

}

NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStoragesharedHTTPCookieStorage];

for(NSHTTPCookie*cookiein[cookieJar cookies]) {

[cookieJar deleteCookie:cookie];

}

这是清缓存,亲测有效

作者:moonCoder

链接:https://www.jianshu.com/p/0f69030e9cb3

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180610B0BGT400?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券