背景
近期,公司項目需要對接第三方公司H5頁面,其中遇到一個WKWebview網(wǎng)頁緩存在每次啟動APP都會無故消失的問題。H5使用的是localStorage,這個應該是H5標準配置,蘋果這么大的公司沒理由會犯這種錯誤吧?于是,一段調試之旅就此開始。
WKWebview的坑
WKWebview的坑很多早有耳聞,但是真正發(fā)生在自己身上這還是第一次。常見的是第一次加載不帶cookies,或者兩個webview之間cookies不共享。但是localStorage和cookies雖有相似之處,但都是緩存,說不定也有同樣的問題,所以開始網(wǎng)上搜索是否有相似問題。

沒想到還真有很多和我一樣類似的問題,因為iOS沒有提供獲取localStorage數(shù)據(jù)的方法,所以只能通過原生調用JS的方式獲取和存儲LocalStorage,于是就引出下面第一個經(jīng)驗。
通過js獲取和存儲localStorage
首先先說思路,第一次加載網(wǎng)頁之前,通過js將本地的localStorage數(shù)據(jù)通過JS腳本加入到網(wǎng)頁localStorage中,然后每次H5更新localStorage數(shù)據(jù)插入完畢,更新一份數(shù)據(jù)到本地沙盒。這樣就能解決第一次不帶localStorage數(shù)據(jù)的問題。下面是引用網(wǎng)上的代碼:
NSString * userContent = [NSString stringWithFormat:@"{\"token\": \"%@\", \"userId\": %@}", @"a1cd4a59-974f-44ab-b264-46400f26c849", @"89"];
// 設置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];
因為這段代碼中的js代碼比較簡單,固定了字段名稱,但是現(xiàn)實中h5頁面很可能增減字段,所以我對這段代碼做了優(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 測試成功獲取到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];
}
}];
}
}
}
}];
先通過js獲取到本地localStorage所有key,然后再逐個獲取值存儲到本地plist文件中。由于考慮到很多js是異步請求執(zhí)行,所以觸發(fā)時機放到didFinishNavigation 2秒延時后調用。
下面再來看看插入localStorage代碼:
- (void)setupLocalStrorageWithConfig:(WKWebViewConfiguration*)configuration dic:(NSMutableDictionary *)dic{
HSLogWithModule(2028,@"***XL 準備插入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 本次啟動插入過,取消插入");
}
}
在WKWebview初始化時配置WKWebViewConfiguration 的userContentController 插入腳本時機為WKUserScriptInjectionTimeAtDocumentStart ,完美。
使用開發(fā)者模式+MAC Safari瀏覽器調試APPweb頁(驚喜)
在驗證上述過程我還有一個意外收獲,原來iOSweb頁的調試最正規(guī)的調試方法是用開發(fā)者模式+MAC Safari瀏覽器!?。?br> 如果你申請過開發(fā)者賬號,并且在手機用開發(fā)者賬號登錄appleid,那么會有一欄開發(fā)者欄,并且在 設置->Safari瀏覽器->高級 中有個網(wǎng)頁檢查器開關,打開它。
然后在MAC電腦中safari瀏覽器的偏好設置里面,打開下方的開發(fā)欄。

然后打開手機要調試的網(wǎng)頁,在Mac開發(fā)欄中選中你的手機,就可以看到需要調試的web頁的所有信息?。。?/p>

再也不用寫輔助JS獲取web信息了!
WKWebview適配localStorage(最終解)
通過上面的方法和工具驗證,我們確實發(fā)現(xiàn)localStorage在APP啟動會消失,并且用js腳本方法成功注入了數(shù)據(jù)。但是有一個問題,web頁每次調用didFinishNavigation都會獲取最新的LocalStorage數(shù)據(jù),并且是遍歷一篇,非常的蠢。為了追求完美,我還是有點不死心的搜索,最終有了驚人的發(fā)現(xiàn)。
其實在WKWebViewConfiguration中有一個websiteDataStore屬性,查了文檔是專門用來存儲本地數(shù)據(jù)的。比如cookies session localStorage,官方文檔如下:

里面明確說明有兩個類型 defaultDataStore 是存儲到本地的,nonPersistentDataStore 是存儲到內(nèi)存的。webview不通對象之所以不會共享緩存,是因為在初始化的時候的config沒有配置websiteDataStore,沒有指定他存儲的地方!所以為了讓webview共享緩存存儲空間,做如下修改

另外還有些網(wǎng)頁說要修改WKProcessPool為單例,為了保險起見,也加上。

問題定位
defaultDataStore就是我們需要的類型,nonPersistentDataStore是那種無痕瀏覽才使用到的。那么問題來了,明明defaultDataStore是存儲到本地硬盤的,那為什么殺死APP會獲取不到localStorage呢?一個可怕的念頭出現(xiàn)了,會不會是APP自己清除了。。。于是我開始搜索websiteDataStore相關代碼,果然在APP啟動的時候調用了這段代碼。。

可能是之前做某些網(wǎng)頁功能時從網(wǎng)上抄的代碼,不理解什么意思就使用了。這段代碼會把本地的所有緩存都清除了,而正常的清除手段應該是根據(jù)URL去刪除其作用域下的緩存。
//清除系統(tǒng)相關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]) //取消備注,可以針對某域名清除,否則是全清
{
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record]
completionHandler:^{
NSLog(@"Cookies for %@ deleted successfully",record.displayName);
}];
}
}
}];
}
總結
至此一個WKWebview首次不加載loaclStorage的問題才根本解決。理論上是一段bug代碼引發(fā)的,但是不清楚為什么網(wǎng)上有那么多的小伙伴和我有一樣的遭遇。。。所以這里寫篇文章,希望能讓有相同情況的小伙伴少走點彎路。