背景
近期活烙,公司項(xiàng)目需要對(duì)接第三方公司H5頁(yè)面徐裸,其中遇到一個(gè)WKWebview網(wǎng)頁(yè)緩存在每次啟動(dòng)APP都會(huì)無(wú)故消失的問(wèn)題。H5使用的是localStorage啸盏,這個(gè)應(yīng)該是H5標(biāo)準(zhǔn)配置重贺,蘋果這么大的公司沒(méi)理由會(huì)犯這種錯(cuò)誤吧?于是,一段調(diào)試之旅就此開(kāi)始气笙。
WKWebview的坑
WKWebview的坑很多早有耳聞次企,但是真正發(fā)生在自己身上這還是第一次。常見(jiàn)的是第一次加載不帶cookies潜圃,或者兩個(gè)webview之間cookies不共享缸棵。但是localStorage和cookies雖有相似之處,但都是緩存谭期,說(shuō)不定也有同樣的問(wèn)題堵第,所以開(kāi)始網(wǎng)上搜索是否有相似問(wèn)題。
沒(méi)想到還真有很多和我一樣類似的問(wèn)題隧出,因?yàn)閕OS沒(méi)有提供獲取localStorage數(shù)據(jù)的方法踏志,所以只能通過(guò)原生調(diào)用JS的方式獲取和存儲(chǔ)LocalStorage,于是就引出下面第一個(gè)經(jīng)驗(yàn)。
通過(guò)js獲取和存儲(chǔ)localStorage
首先先說(shuō)思路胀瞪,第一次加載網(wǎng)頁(yè)之前针余,通過(guò)js將本地的localStorage數(shù)據(jù)通過(guò)JS腳本加入到網(wǎng)頁(yè)localStorage中,然后每次H5更新localStorage數(shù)據(jù)插入完畢凄诞,更新一份數(shù)據(jù)到本地沙盒圆雁。這樣就能解決第一次不帶localStorage數(shù)據(jù)的問(wèn)題。下面是引用網(wǎng)上的代碼:
NSString * userContent = [NSString stringWithFormat:@"{\"token\": \"%@\", \"userId\": %@}", @"a1cd4a59-974f-44ab-b264-46400f26c849", @"89"];
// 設(shè)置localStorage
NSString *jsString = [NSString stringWithFormat:@"localStorage.setItem('userContent', '%@')", userContent];
// 移除localStorage
// NSString *jsString = @"localStorage.removeItem('userContent')";
// 獲取localStorage
// NSString *jsString = @"localStorage.getItem('userContent')";
[self.webView evaluateJavaScript:jsString completionHandler:nil];
因?yàn)檫@段代碼中的js代碼比較簡(jiǎn)單帆谍,固定了字段名稱摸柄,但是現(xiàn)實(shí)中h5頁(yè)面很可能增減字段,所以我對(duì)這段代碼做了優(yōu)化:
NSString * jsStr = @"var count = localStorage.length; var arr = new Array();for(var i=0;i<count;i++){ var key = localStorage.key(i);arr[i]= key;} arr;";
[webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable data , NSError * _Nullable error) {
if (data && ![data isKindOfClass:[NSNull class]]) {
HSLogWithModule(2028,@"***XL 測(cè)試成功獲取到localStorage所有數(shù)據(jù)%@",data);
if ([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSMutableArray class]]) {
NSArray * arr = [data copy];
for (NSInteger i = 0; i < arr.count; i ++) {
NSString * key =StringFromObject([NSString stringWithFormat:@"%@",arr[i]]);
[webView evaluateJavaScript:[NSString stringWithFormat:@"localStorage.getItem('%@')",key] completionHandler:^(id data, NSError * _Nullable error) {
if (data && ![data isKindOfClass:[NSNull class]]) {
HSLogWithModule(2028,@"***XL 獲取%@成功2 %@",key,data);
[self.localStorageDic setObject:[NSString stringWithFormat:@"%@",data] forKey:key];
[self.plistHelper WritePlistFileToDisk:self.localStorageDic];
}
}];
}
}
}
}];
先通過(guò)js獲取到本地localStorage所有key既忆,然后再逐個(gè)獲取值存儲(chǔ)到本地plist文件中驱负。由于考慮到很多js是異步請(qǐng)求執(zhí)行,所以觸發(fā)時(shí)機(jī)放到didFinishNavigation 2秒延時(shí)后調(diào)用患雇。
下面再來(lái)看看插入localStorage代碼:
- (void)setupLocalStrorageWithConfig:(WKWebViewConfiguration*)configuration dic:(NSMutableDictionary *)dic{
HSLogWithModule(2028,@"***XL 準(zhǔn)備插入localStorage");
if (![HSXLManager shareManager].haveLoadStorage) {
NSString *jsString = @"";
NSArray * keys = [dic allKeys];
for (NSInteger i = 0;i < keys.count; i ++){
NSString * key = keys[i];
NSString * value = [dic objectForKey:key];
jsString = [NSString stringWithFormat:@"%@ %@;", jsString,[NSString stringWithFormat:@"localStorage.setItem('%@', '%@')",key, value]];
}
HSLogWithModule(2028,@"***XL 腳本%@",jsString);
[configuration.userContentController addUserScript:[[WKUserScript alloc] initWithSource:jsString injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]];
HSLogWithModule(2028,@"***XL 插入腳本完成");
[HSXLManager shareManager].haveLoadStorage = YES;
}
else {
HSLogWithModule(2028,@"***XL 本次啟動(dòng)插入過(guò)跃脊,取消插入");
}
}
在WKWebview初始化時(shí)配置WKWebViewConfiguration 的userContentController 插入腳本時(shí)機(jī)為WKUserScriptInjectionTimeAtDocumentStart ,完美苛吱。
使用開(kāi)發(fā)者模式+MAC Safari瀏覽器調(diào)試APPweb頁(yè)(驚喜)
在驗(yàn)證上述過(guò)程我還有一個(gè)意外收獲酪术,原來(lái)iOSweb頁(yè)的調(diào)試最正規(guī)的調(diào)試方法是用開(kāi)發(fā)者模式+MAC Safari瀏覽器!4浯ⅰ绘雁!
如果你申請(qǐng)過(guò)開(kāi)發(fā)者賬號(hào),并且在手機(jī)用開(kāi)發(fā)者賬號(hào)登錄appleid援所,那么會(huì)有一欄開(kāi)發(fā)者欄庐舟,并且在 設(shè)置->Safari瀏覽器->高級(jí) 中有個(gè)網(wǎng)頁(yè)檢查器開(kāi)關(guān),打開(kāi)它住拭。
然后在MAC電腦中safari瀏覽器的偏好設(shè)置里面挪略,打開(kāi)下方的開(kāi)發(fā)欄历帚。
然后打開(kāi)手機(jī)要調(diào)試的網(wǎng)頁(yè),在Mac開(kāi)發(fā)欄中選中你的手機(jī)杠娱,就可以看到需要調(diào)試的web頁(yè)的所有信息M炖巍!摊求!
再也不用寫輔助JS獲取web信息了禽拔!
WKWebview適配localStorage(最終解)
通過(guò)上面的方法和工具驗(yàn)證,我們確實(shí)發(fā)現(xiàn)localStorage在APP啟動(dòng)會(huì)消失室叉,并且用js腳本方法成功注入了數(shù)據(jù)奏赘。但是有一個(gè)問(wèn)題,web頁(yè)每次調(diào)用didFinishNavigation都會(huì)獲取最新的LocalStorage數(shù)據(jù)太惠,并且是遍歷一篇磨淌,非常的蠢。為了追求完美凿渊,我還是有點(diǎn)不死心的搜索梁只,最終有了驚人的發(fā)現(xiàn)。
其實(shí)在WKWebViewConfiguration中有一個(gè)websiteDataStore屬性埃脏,查了文檔是專門用來(lái)存儲(chǔ)本地?cái)?shù)據(jù)的搪锣。比如cookies session localStorage,官方文檔如下:
里面明確說(shuō)明有兩個(gè)類型 defaultDataStore 是存儲(chǔ)到本地的,nonPersistentDataStore 是存儲(chǔ)到內(nèi)存的彩掐。webview不通對(duì)象之所以不會(huì)共享緩存构舟,是因?yàn)樵诔跏蓟臅r(shí)候的config沒(méi)有配置websiteDataStore,沒(méi)有指定他存儲(chǔ)的地方堵幽!所以為了讓webview共享緩存存儲(chǔ)空間狗超,做如下修改
另外還有些網(wǎng)頁(yè)說(shuō)要修改WKProcessPool為單例,為了保險(xiǎn)起見(jiàn)朴下,也加上努咐。
問(wèn)題定位
defaultDataStore就是我們需要的類型,nonPersistentDataStore是那種無(wú)痕瀏覽才使用到的殴胧。那么問(wèn)題來(lái)了渗稍,明明defaultDataStore是存儲(chǔ)到本地硬盤的,那為什么殺死APP會(huì)獲取不到localStorage呢团滥?一個(gè)可怕的念頭出現(xiàn)了竿屹,會(huì)不會(huì)是APP自己清除了。灸姊。拱燃。于是我開(kāi)始搜索websiteDataStore相關(guān)代碼,果然在APP啟動(dòng)的時(shí)候調(diào)用了這段代碼厨钻。扼雏。
可能是之前做某些網(wǎng)頁(yè)功能時(shí)從網(wǎng)上抄的代碼,不理解什么意思就使用了夯膀。這段代碼會(huì)把本地的所有緩存都清除了诗充,而正常的清除手段應(yīng)該是根據(jù)URL去刪除其作用域下的緩存。
//清除系統(tǒng)相關(guān)cookies
+ (void)delCookiesWithDomain:(NSString *)domain{
if (domain.length <=0) {
return;
}
WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
[dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
for (WKWebsiteDataRecord *record in records)
{
NSLog(@"**[XL]**刪除Web緩存**%@**type:%@*",record.displayName,record.dataTypes);
if ( [record.displayName containsString:domain]) //取消備注诱建,可以針對(duì)某域名清除蝴蜓,否則是全清
{
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record]
completionHandler:^{
NSLog(@"Cookies for %@ deleted successfully",record.displayName);
}];
}
}
}];
}
總結(jié)
至此一個(gè)WKWebview首次不加載loaclStorage的問(wèn)題才根本解決。理論上是一段bug代碼引發(fā)的俺猿,但是不清楚為什么網(wǎng)上有那么多的小伙伴和我有一樣的遭遇茎匠。。押袍。所以這里寫篇文章诵冒,希望能讓有相同情況的小伙伴少走點(diǎn)彎路。