iOS WKWebview首次加载LocalStorage 问题

2021-11-30  本文已影响0人  o翻滚的牛宝宝o

背景


近期,公司项目需要对接第三方公司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代码引发的,但是不清楚为什么网上有那么多的小伙伴和我有一样的遭遇。。。所以这里写篇文章,希望能让有相同情况的小伙伴少走点弯路。

上一篇下一篇

猜你喜欢

热点阅读