在iOS 8.0以后蘋果推出WKWebView,之前有性能問(wèn)題的UIWebView基本就被棄用了,這里整理下我的WKWebView之旅和怎么封裝的。
1、WKWebView有個(gè)繞不過(guò)去的問(wèn)題就是Cookie.
我們先來(lái)看下Cookie到底是個(gè)什么東西:
簡(jiǎn)單地說(shuō),cookie 就是瀏覽器儲(chǔ)存在用戶電腦上的一小段文本文件。cookie 是純文本格式,不包含任何可執(zhí)行的代碼。一個(gè) Web 頁(yè)面或服務(wù)器告知瀏覽器按照一定規(guī)范來(lái)儲(chǔ)存這些信息,并在隨后的請(qǐng)求中將這些信息發(fā)送至服務(wù)器,Web 服務(wù)器就可以使用這些信息來(lái)識(shí)別不同的用戶。大多數(shù)需要登錄的網(wǎng)站在用戶驗(yàn)證成功之后都會(huì)設(shè)置一個(gè) cookie,只要這個(gè) cookie 存在并可以,用戶就可以自由瀏覽這個(gè)網(wǎng)站的任意頁(yè)面。再次說(shuō)明,cookie 只包含數(shù)據(jù),就其本身而言并不有害。緊跟 cookie 值后面的每個(gè)選項(xiàng)都以分號(hào)和空格分開(kāi),每個(gè)選擇都指定了 cookie 在什么情況下應(yīng)該被發(fā)送至服務(wù)器。第一個(gè)選項(xiàng)是過(guò)期時(shí)間(expires),指定了 cookie 何時(shí)不會(huì)再被發(fā)送至服務(wù)器,隨后瀏覽器將刪除該 cookie。該選項(xiàng)的值是一個(gè) Wdy, DD-Mon-YYYY HH:MM:SS GMT 日期格式的值。下一個(gè)選項(xiàng)是 domain,指定了 cookie 將要被發(fā)送至哪個(gè)或哪些域中。默認(rèn)情況下,domain會(huì)被設(shè)置為創(chuàng)建該 cookie 的頁(yè)面所在的域名,所以當(dāng)給相同域名發(fā)送請(qǐng)求時(shí)該 cookie 會(huì)被發(fā)送至服務(wù)器。另一個(gè)控制 Cookie 消息頭發(fā)送時(shí)機(jī)的選項(xiàng)是 path 選項(xiàng),和 domain 選項(xiàng)類似,path選項(xiàng)指定了請(qǐng)求的資源 URL 中必須存在指定的路徑時(shí),才會(huì)發(fā)送Cookie 消息頭。這個(gè)比較通常是將 path 選項(xiàng)的值與請(qǐng)求的 URL 從頭開(kāi)始逐字符比較完成的。如果字符匹配,則發(fā)送 Cookie 消息頭。最后一個(gè)選項(xiàng)是 secure。不像其它選項(xiàng),該選項(xiàng)只是一個(gè)標(biāo)記而沒(méi)有值。只有當(dāng)一個(gè)請(qǐng)求通過(guò) SSL 或 HTTPS 創(chuàng)建時(shí),包含 secure 選項(xiàng)的 cookie 才能被發(fā)送至服務(wù)器。這種 cookie 的內(nèi)容具有很高的價(jià)值,如果以純文本形式傳遞很有可能被篡改。
UIWebView Cookie
同一個(gè)應(yīng)用,不同UIWebView之間的Cookie是自動(dòng)同步的。并且可以被其他網(wǎng)絡(luò)類訪問(wèn)比如NSURLConnection,AFNetworking。
它們都是保存在NSHTTPCookieStorage容器中。 當(dāng)UIWebView加載一個(gè)URL的時(shí)候,在加載完成時(shí)候,Http Response,對(duì)Cookie進(jìn)行寫入,更新或者刪除,結(jié)果更新Cookie到NSHTTPCookieStorage存儲(chǔ)容器中。
WKWebView Cookie
NSURLCache和NSHTTPCookieStroage無(wú)法操作(WKWebView)WebCore進(jìn)程的緩存和Cookie。
WKWebView實(shí)例將會(huì)忽略任何的默認(rèn)網(wǎng)絡(luò)存儲(chǔ)器(NSURLCache, NSHTTPCookieStorage, NSCredentialStorage) 和一些標(biāo)準(zhǔn)的自定義網(wǎng)絡(luò)請(qǐng)求類(NSURLProtocol,等等.)。
WKWebView實(shí)例不會(huì)把Cookie存入到App標(biāo)準(zhǔn)的的Cookie容器(NSHTTPCookieStorage)中,因?yàn)?NSURLSession/NSURLConnection等網(wǎng)絡(luò)請(qǐng)求使用NSHTTPCookieStorage進(jìn)行訪問(wèn)Cookie,所以不能訪問(wèn)WKWebView的Cookie,現(xiàn)象就是WKWebView存了Cookie,其他的網(wǎng)絡(luò)類如NSURLSession/NSURLConnection卻看不到。,
與Cookie相同的情況就是WKWebView的緩存,憑據(jù)等。WKWebView都擁有自己的私有存儲(chǔ),因此和標(biāo)準(zhǔn)Cocoa網(wǎng)絡(luò)類兼容的不是那么好。
你也不能自定義requests(增加自己的http header,更改已經(jīng)存在的header)使用自定義的 URL schemes等等,因?yàn)镹SURLProtocol也是不支持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 @"";
}
劃重點(diǎn):坑一:JS注入的Cookie,比如PHP代碼在Cookie容器中取是取不到的, javascript document.cookie能讀取到,瀏覽器中也能看到。
NSMutableURLRequest 注入的PHP等動(dòng)態(tài)語(yǔ)言直接能從$_COOKIE對(duì)象中獲取到,但是js讀取不到,瀏覽器也看不到
所以合理的辦法讓js,php,瀏覽器都能讀取到相同的Cookie方法就是創(chuàng)建WebView的時(shí)候javascript注入Cookie,一開(kāi)始發(fā)送NSMutableURLRequest請(qǐng)求的時(shí)候也要加上Cookie,并且保證兩個(gè)地方的設(shè)置的cookie一致。
坑二:WKWebView的cookie需要設(shè)置domain和path默認(rèn)情況下會(huì)帶進(jìn)去不是通用的。(今天剛發(fā)現(xiàn)的o)
坑三:網(wǎng)頁(yè)登錄跳原生之后登錄成功后dimis后你需要重新注入和刷新把cookie塞進(jìn)去,所以你的viewWillAppear每次都需要重新加載WKWebView和重新loadRequest,簡(jiǎn)直了。。。
KZWWebViewController 是如何做的呢?
我們知道,app里經(jīng)常跳各種網(wǎng)頁(yè),我們不可能每個(gè)網(wǎng)頁(yè)都去單獨(dú)處理,所以我們寫一個(gè)通用的可配置的KZWWebViewController,只需要傳url進(jìn)來(lái)就可以,其他你不要管了,是不是完美。
所以我們需要來(lái)設(shè)計(jì)一個(gè)這樣的KZWWebViewController,首先必須的是跳轉(zhuǎn)的url和其他的配置參數(shù),然后是接入jsbridge,方便我們和網(wǎng)頁(yè)的換下調(diào)用,這樣我們的這個(gè)KZWWebViewController就基本滿足需求了。
所以我的做法是抽出一個(gè)類來(lái)管理,它叫KZWRouterHelper,暴露一個(gè)方法
+ (void)pushbyPath:(NSString *)path xxx(xxx)xx .....
里面的操作是把你需要的配置的加上,然后轉(zhuǎn)成字典塞入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]]
};
這個(gè)根據(jù)業(yè)務(wù)需求來(lái)配置就好,然后在controller里根據(jù)不同的參數(shù)做相應(yīng)的處理就可以了,這樣你的整個(gè)項(xiàng)目里所有的網(wǎng)頁(yè)跳轉(zhuǎn)就一行代碼就好了:
[KZWRouterHelper pushbyURL:@"xxxx" ];
接入的jsbirdge最好是選擇jscore的方式的,這樣是同步,網(wǎng)頁(yè)也可以加個(gè)配置方法,這個(gè)的主要目的,有的網(wǎng)頁(yè)需要由網(wǎng)頁(yè)自己來(lái)控制一些顯示,原理同上我們自己的配置都是根據(jù)參數(shù)做不同的處理,具體看KZWWebViewController,然后很多時(shí)候產(chǎn)品想要跳二級(jí)頁(yè)面的時(shí)候可以有2個(gè)返回,這時(shí)候如果你的leftBarButtonItems是統(tǒng)配的話最好在頁(yè)面加載的時(shí)候就先設(shè)置一個(gè)返回,然后在didFinishNavigation代理設(shè)置成2個(gè)返回,一個(gè)返回上一個(gè)網(wǎng)頁(yè)一個(gè)返回我們上一個(gè)控制器。這樣做主要是你開(kāi)始設(shè)置成統(tǒng)配后來(lái)直接配2個(gè)會(huì)閃一下。
還有就是WKWebView中的進(jìn)度條,WKWebView的進(jìn)度條比較簡(jiǎn)單你只要寫一個(gè)UIProgressView然后監(jiān)聽(tīng)WKWebView的加載進(jìn)度就好了,然后title的顯示也是監(jiān)聽(tīng)就好了,如果沒(méi)取到記得設(shè)置一個(gè)默認(rèn)的。
[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;
}
最后記得釋放你的監(jiān)聽(tīng):
- (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的時(shí)候可以加個(gè)環(huán)境的配置。
就完啦,希望對(duì)你有用。具體看KZWFoudation中的KZWWebViewController,KZWRouterHelper和KZWDSJavaScripInterface。https://github.com/ouyrp/KZWFoundation
哦還有一個(gè)cookie的清空和網(wǎng)頁(yè)清緩存我也加上吧,前2篇關(guān)于WKWebView的文章就刪了。
- (NSString *)readCurrentCookie {
return @"";
}
- (NSString *)setCurrentCookie {
return @"";
}
cookie清空只要這個(gè)2個(gè)方法里面參數(shù)清了就好了
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];
}
這是清緩存,親測(cè)有效
之后還是會(huì)出KZWFoudation的系列文章,寫下自己的封裝思路。