iOS11.3 WKWebView清除cookie所踩的坑!

我们在iOS11.3上踩了一个大坑!这个坑表现在线上的版本只要用户升级到iOS11.3且退出登录,或者登录态过期,都会闪退。因为iOS11.3支持了两个超级一线城市的公交卡,引发了用户短时间集中更新。为了解决这个问题,整个团队发一个小版本来降级这个问题的影响。

问题的表现:

前面说过,只要退出登录,App就会闪退。这是因为我们在退出登录时清除WKWebView的cookie时引发了崩溃。

问题的原因:

苹果应该在iOS11.3上修改了nonPersistentDataStore创建的DataStore的实现。

问题复盘:

我们先回归一下WKWebView下cookie的前世今生。cookie本质上是h5用来保存登录、用户名、以及一些特定时间内有效的信息的机制。这个东西有几个显而易见的好处:

1、本地化的存储一些信息,可以减少h5请求的次数。提升h5的性能,降低服务端压力;

2、cookie作为h5请求request的一部分,会把一些客户端的固定信息直接传给服务端,避免h5因为某些信息又需要发送网络请求;

3、cookie是域名隔离的,有一定的安全性;

正因为有这么多的好处,所以,很多时候h5大量的依赖了cookie机制。甚至用它保存了一些敏感信息。这些信息的泄露会对用户的安全造成重大威胁。所以苹果在iOS8引入WKWebView时,没有给原生暴露任何操控cookie的API(事实上,不仅cookie,其他任何和缓存、持久化相关的接口都没有)。我的理解是:苹果有意识将cookie对开发者进行了屏蔽,就是不希望原生开发者对cookie进行干预。而cookie的安全性完全由h5来保证。讲道理这么做无可厚非。但是有个东西很可怕----那就是用户习惯。在原来老的UIWebView下,以及其他的浏览器中熟悉了各种cookie操作的开发者肯定不会就此罢休。各种携带cookie的“黑”操作层出不穷。所以,从iOS9开始,苹果暴露了WKWebView数据存储的类----WKWebsiteDataStore。顾名思义,这个类暴露了一些获取(清除)特定缓存的能力,这里包括了cookie。下面稍微介绍一下这些能力

+ (NSSet<NSString *> *)allWebsiteDataTypes;

可以获取dataStore中所有的数据类型。这里有:

    WKWebsiteDataTypeDiskCache,//硬盘缓存
    WKWebsiteDataTypeOfflineWebApplicationCache,//离线应用缓存
    WKWebsiteDataTypeMemoryCache,//内存缓存
    WKWebsiteDataTypeLocalStorage,//localStorage,cookie的一个兄弟
    WKWebsiteDataTypeCookies,//cookie
    WKWebsiteDataTypeSessionStorage,//session
    WKWebsiteDataTypeIndexedDBDatabases,//索引数据库
    WKWebsiteDataTypeWebSQLDatabases//数据库

这里可以看出,与h5相关的缓存都暴露给了原生。

-(void)fetchDataRecordsOfTypes:(NSSet<NSString*>*)dataTypes completionHandler:(void(^)(NSArray<WKWebsiteDataRecord *> *))completionHandler;
获取某些指定的数据类型。最后数据实例用一个统一的抽象的WKWebsiteDataRecord来表示。
-(void)removeDataOfTypes:(NSSet<NSString*>*)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler;

移除某些特定的数据类型。


- (void)removeDataOfTypes:(NSSet<NSString*> *)websiteDataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;

移除指定时期的特定的数据类型。


从以上可以看出,苹果暴露了各种缓存的获取方法和移除方法。但是没有给设置方法。而且cookie并不突出。和其他兄弟一样。所以苹果的本意还是不希望原生直接干预cookie的设置。


但是从iOS11开始,一切都变了。苹果彻底放开了cookie的操作权限。它在WKWebsiteDataStore中暴露了一个

WKHTTPCookieStore类型的属性。专门用来管理cookie。cookie摇身一变,从嫔妃变成了皇后。位置特殊了。通过这个属性,原生可以自由的移除cookie和设置cookie了(是不是和以前的NSHttpCookieStorage一样了?)。


这种设计带来了一个问题?现在在iOS8、9、10、11都存在的情况下,我们如何清除cookie。iOS8没有暴露接口,用户量也不大了,我们不做考虑。iOS9,10只有一种方式。iOS11有两种(注意苹果暴露了WKHTTPCookieStore的属性来操作cookie,但是老的API并没有废弃,哪怕是和cookie相关的那部分也没有废弃)。我想看到这个问题,脑子比较正常的人都会想:


iOS11采用新的API就行了啊。


但总有脑子不正常的人,比如我。我原本是想这么干的。但是后来一想,老的API没有废弃,那就肯定可以用,那我继续用它呗。但是有个问题,iOS11既然暴露了新的接口,这是不是意味着老的API可能清除cookie会失败呢?或者后续哪个版本万一失败呢?所以,我用了一种比较“保险”的方案。那就是先用老的API去清除,然后用新的API去校验。伪代码可以这么来看:


old api fetch cookie:
finish{
   old api remove cookie:
   finish{
        new api get cookie:
        finish{
           if cookie still exist
              new api clear cookie
        }
   }
}

逻辑上没有毛病!!!我到目前为止仍然认为逻辑上没有毛病。


下面开始清除。在清除的时候我们发现WKWebsiteDataStore有两个类方法:

/* @abstract Returns the default data store. */
+ (WKWebsiteDataStore *)defaultDataStore;

/** @abstract Returns a new non-persistent data store.
 @discussion If a WKWebView is associated with a non-persistent data store, no data will
 be written to the file system. This is useful for implementing "private browsing" in a web view.
*/
+ (WKWebsiteDataStore *)nonPersistentDataStore;

字面意思很清楚,第二个是非持久的dataStore, 那第一个就是持久的。我的做法是分别去清除这两类dataStore中的cookie。需求做完没有毛病,一直很健康,退出登录时cookie被清除的寸草不生。我暗自欣慰。

但是iOS11.3推出后,登陆的同事反馈,闪退。瞎了我的钛合金狗眼。为啥,再经历了mac系统升级、xcode升级、手机操作系统升级等一系列变态操作后,真相来了:

我们上述方法在清除nonPersistentDataStore时当执行到用iOS11的API去校验时就挂了。崩溃的栈显示WKWebsiteDataStore正在析构。WTF!!!!

问题的原因大概就清晰了:

defaultDataStore不挂,原因是它类似于单例,代码执行过程中不会析构!

nonPersistentDataStore 以前不挂,现在开始挂,说明以前苹果对它的实现也是单例,而现在它应该不是单例了。我们创建它的实例是在函数内部,当出了方法作用域后它就会析构了。执行iOS11 API检验时它正在析构,向一个正在析构的对象发消息它是会挂的。

现在我们回头来看nonPersistentDataStore的说明:

 @abstract Returns a new non-persistent data store.
 @discussion If a WKWebView is associated with a non-persistent data store, no data will
 be written to the file system. This is useful for implementing "private browsing" in a web view.

和前面持久化的dataStore不同的是,nonPersistentDataStore用于实现私密的浏览器,即该浏览器的数据与其他WebView不共享。苹果本质的意愿应该是一个WKWebView和一个nonPersistentDataStore的绑定。以前的策略应该是多个WKWebView可以和一个non-persistent的datastore绑定。只要它和普通的datastore隔离即可。(当然,以上都是通过现象的推测。如果有人有确定说明,欢迎指正)

至此,这个坑介绍完毕!我们来总结一下这里我们的失误:

第一、对nonPersistentDataStore缺少比较全面的理解,如果理解了它的用途,其实可以判定当前我们的App是没有使用场景的;

第二、iOS11和iOS9、10的API最好不要嵌套使用。其实我们当时做的时候就发现老的API的效果是ok的。只不过不放心,用一种莫须有的心态增加了一个逻辑。其实这个逻辑即使有必要,也要经过验证,证明老的API确实无法完成再去完善,毕竟cookie即使清除不了,也不是天塌下来的事。后续完善即可。这种前期画蛇添足的思维方式还是要多改变!!!

完!


猜你喜欢

转载自blog.csdn.net/u012413955/article/details/79783282
今日推荐