iOS WKWebview首次加載LocalStorage 問題解決經(jīng)歷

背景


近期,公司項(xiàng)目需要對接第三方公司H5頁面,其中遇到一個(gè)WKWebview網(wǎng)頁緩存在每次啟動(dòng)APP都會(huì)無故消失的問題。H5使用的是localStorage,這個(gè)應(yīng)該是H5標(biāo)準(zhǔn)配置,蘋果這么大的公司沒理由會(huì)犯這種錯(cuò)誤吧?于是,一段調(diào)試之旅就此開始。

WKWebview的坑


WKWebview的坑很多早有耳聞,但是真正發(fā)生在自己身上這還是第一次。常見的是第一次加載不帶cookies,或者兩個(gè)webview之間cookies不共享。但是localStorage和cookies雖有相似之處,但都是緩存,說不定也有同樣的問題,所以開始網(wǎng)上搜索是否有相似問題。


截屏2021-11-30 上午9.47.24.png

沒想到還真有很多和我一樣類似的問題,因?yàn)閕OS沒有提供獲取localStorage數(shù)據(jù)的方法,所以只能通過原生調(diào)用JS的方式獲取和存儲(chǔ)LocalStorage,于是就引出下面第一個(gè)經(jīng)驗(yàn)。

通過js獲取和存儲(chǔ)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"];
// 設(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代碼比較簡單,固定了字段名稱,但是現(xiàn)實(shí)中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,然后再逐個(gè)獲取值存儲(chǔ)到本地plist文件中。由于考慮到很多js是異步請求執(zhí)行,所以觸發(fā)時(shí)機(jī)放到didFinishNavigation 2秒延時(shí)后調(diào)用。

下面再來看看插入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)插入過,取消插入");
  }

}

在WKWebview初始化時(shí)配置WKWebViewConfiguration 的userContentController 插入腳本時(shí)機(jī)為WKUserScriptInjectionTimeAtDocumentStart ,完美。

使用開發(fā)者模式+MAC Safari瀏覽器調(diào)試APPweb頁(驚喜)


在驗(yàn)證上述過程我還有一個(gè)意外收獲,原來iOSweb頁的調(diào)試最正規(guī)的調(diào)試方法是用開發(fā)者模式+MAC Safari瀏覽器!?。?br> 如果你申請過開發(fā)者賬號(hào),并且在手機(jī)用開發(fā)者賬號(hào)登錄appleid,那么會(huì)有一欄開發(fā)者欄,并且在 設(shè)置->Safari瀏覽器->高級 中有個(gè)網(wǎng)頁檢查器開關(guān),打開它。


網(wǎng)頁檢查器.PNG

然后在MAC電腦中safari瀏覽器的偏好設(shè)置里面,打開下方的開發(fā)欄。


mac1.png

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


mac2.png

再也不用寫輔助JS獲取web信息了!

WKWebview適配localStorage(最終解)


通過上面的方法和工具驗(yàn)證,我們確實(shí)發(fā)現(xiàn)localStorage在APP啟動(dòng)會(huì)消失,并且用js腳本方法成功注入了數(shù)據(jù)。但是有一個(gè)問題,web頁每次調(diào)用didFinishNavigation都會(huì)獲取最新的LocalStorage數(shù)據(jù),并且是遍歷一篇,非常的蠢。為了追求完美,我還是有點(diǎn)不死心的搜索,最終有了驚人的發(fā)現(xiàn)。

其實(shí)在WKWebViewConfiguration中有一個(gè)websiteDataStore屬性,查了文檔是專門用來存儲(chǔ)本地?cái)?shù)據(jù)的。比如cookies session localStorage,官方文檔如下:


官方文檔.png

里面明確說明有兩個(gè)類型 defaultDataStore 是存儲(chǔ)到本地的,nonPersistentDataStore 是存儲(chǔ)到內(nèi)存的。webview不通對象之所以不會(huì)共享緩存,是因?yàn)樵诔跏蓟臅r(shí)候的config沒有配置websiteDataStore,沒有指定他存儲(chǔ)的地方!所以為了讓webview共享緩存存儲(chǔ)空間,做如下修改


如下修改.png

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

參考建議.png

問題定位


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


問題定位.png

可能是之前做某些網(wǎng)頁功能時(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]) //取消備注,可以針對某域名清除,否則是全清
           {
                [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes   forDataRecords:@[record]
                                                     completionHandler:^{
                   NSLog(@"Cookies for %@ deleted successfully",record.displayName);
                }];
           }
       }
   }];
}

總結(jié)


至此一個(gè)WKWebview首次不加載loaclStorage的問題才根本解決。理論上是一段bug代碼引發(fā)的,但是不清楚為什么網(wǎng)上有那么多的小伙伴和我有一樣的遭遇。。。所以這里寫篇文章,希望能讓有相同情況的小伙伴少走點(diǎn)彎路。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容