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