iOS WKWebview首次加载LocalStorage 问题解决经历

背景


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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容