美文网首页
iOS WKWebview首次加载LocalStorage 问题

iOS WKWebview首次加载LocalStorage 问题

作者: o翻滚的牛宝宝o | 来源:发表于2021-11-30 13:56 被阅读0次

    背景


    近期,公司项目需要对接第三方公司H5页面,其中遇到一个WKWebview网页缓存在每次启动APP都会无故消失的问题。H5使用的是localStorage,这个应该是H5标准配置,苹果这么大的公司没理由会犯这种错误吧?于是,一段调试之旅就此开始。

    WKWebview的坑


    WKWebview的坑很多早有耳闻,但是真正发生在自己身上这还是第一次。常见的是第一次加载不带cookies,或者两个webview之间cookies不共享。但是localStorage和cookies虽有相似之处,但都是缓存,说不定也有同样的问题,所以开始网上搜索是否有相似问题。


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

    没想到还真有很多和我一样类似的问题,因为iOS没有提供获取localStorage数据的方法,所以只能通过原生调用JS的方式获取和存储LocalStorage,于是就引出下面第一个经验。

    通过js获取和存储localStorage


    首先先说思路,第一次加载网页之前,通过js将本地的localStorage数据通过JS脚本加入到网页localStorage中,然后每次H5更新localStorage数据插入完毕,更新一份数据到本地沙盒。这样就能解决第一次不带localStorage数据的问题。下面是引用网上的代码:

    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代码比较简单,固定了字段名称,但是现实中h5页面很可能增减字段,所以我对这段代码做了优化:

     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所有数据%@",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是异步请求执行,所以触发时机放到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 ,完美。

    使用开发者模式+MAC Safari浏览器调试APPweb页(惊喜)


    在验证上述过程我还有一个意外收获,原来iOSweb页的调试最正规的调试方法是用开发者模式+MAC Safari浏览器!!!
    如果你申请过开发者账号,并且在手机用开发者账号登录appleid,那么会有一栏开发者栏,并且在 设置->Safari浏览器->高级 中有个网页检查器开关,打开它。


    网页检查器.PNG

    然后在MAC电脑中safari浏览器的偏好设置里面,打开下方的开发栏。


    mac1.png

    然后打开手机要调试的网页,在Mac开发栏中选中你的手机,就可以看到需要调试的web页的所有信息!!!


    mac2.png

    再也不用写辅助JS获取web信息了!

    WKWebview适配localStorage(最终解)


    通过上面的方法和工具验证,我们确实发现localStorage在APP启动会消失,并且用js脚本方法成功注入了数据。但是有一个问题,web页每次调用didFinishNavigation都会获取最新的LocalStorage数据,并且是遍历一篇,非常的蠢。为了追求完美,我还是有点不死心的搜索,最终有了惊人的发现。

    其实在WKWebViewConfiguration中有一个websiteDataStore属性,查了文档是专门用来存储本地数据的。比如cookies session localStorage,官方文档如下:


    官方文档.png

    里面明确说明有两个类型 defaultDataStore 是存储到本地的,nonPersistentDataStore 是存储到内存的。webview不通对象之所以不会共享缓存,是因为在初始化的时候的config没有配置websiteDataStore,没有指定他存储的地方!所以为了让webview共享缓存存储空间,做如下修改


    如下修改.png

    另外还有些网页说要修改WKProcessPool为单例,为了保险起见,也加上。

    参考建议.png

    问题定位


    defaultDataStore就是我们需要的类型,nonPersistentDataStore是那种无痕浏览才使用到的。那么问题来了,明明defaultDataStore是存储到本地硬盘的,那为什么杀死APP会获取不到localStorage呢?一个可怕的念头出现了,会不会是APP自己清除了。。。于是我开始搜索websiteDataStore相关代码,果然在APP启动的时候调用了这段代码。。


    问题定位.png

    可能是之前做某些网页功能时从网上抄的代码,不理解什么意思就使用了。这段代码会把本地的所有缓存都清除了,而正常的清除手段应该是根据URL去删除其作用域下的缓存。

    //清除系统相关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代码引发的,但是不清楚为什么网上有那么多的小伙伴和我有一样的遭遇。。。所以这里写篇文章,希望能让有相同情况的小伙伴少走点弯路。

    相关文章

      网友评论

          本文标题:iOS WKWebview首次加载LocalStorage 问题

          本文链接:https://www.haomeiwen.com/subject/turdxrtx.html