布景

越来越多的App安装到用户的手机上,导致iPhone的磁盘不足。笔者就经常需求去删去一些缓存、卸载一些不用的App,来削减咱们磁盘空间的占用,这个时分,就会去通用设置界面,去把一些磁盘占用大的App删去掉。来释放一些磁盘空间。各个App厂商都在研究如何削减磁盘缓存占用,降低App被卸载的风险。

WKWebView磁盘办理

本章仅评论的规模是WKWebView的磁盘办理,假如需求了解WKWebView的其他技术点,不在本文章评论规模。WKWebView从iOS9之后供给了一个新的类WKWebsiteDataStore来表示选中网站的各种类型的数据,包含

  • Cookies
  • Disk and Memory caches
  • Persistent data such as WebSQL
  • IndexedDB databases
  • Local storage

关于Disk Cache,WKWebView内部有一套机制办理。经过阅览WebKit的源码能够发现:

 void  Storage::shrinkIfNeeded()
{
  ASSERT(RunLoop::isMain());
  // Avoid randomness caused by cache shrinks.
   if  (m_mode == Mode::AvoidRandomness)
    return;
   if  (approximateSize() > m_capacity)
    shrink();
}

能够看到,每次写入某个需求缓存的内容,都会判断是否需求整理内存,其中m_capacity的设置是根据当前手机的磁盘可用容量初始化的时分设置的

uint64_t calculateURLCacheDiskCapacity(CacheModel cacheModel, uint64_t diskFreeSize)
{
  uint64_t urlCacheDiskCapacity;
  switch  (cacheModel) {
      //...
    case  CacheModel::PrimaryWebBrowser: {
    // Disk cache capacity (in bytes)
    if (diskFreeSize >= 16384)
      urlCacheDiskCapacity = 1 * GB;
    else if (diskFreeSize >= 8192)
      urlCacheDiskCapacity = 500 * MB;
    else if (diskFreeSize >= 4096)
      urlCacheDiskCapacity = 250 * MB;
    else if  (diskFreeSize >= 2048)
      urlCacheDiskCapacity = 200 * MB;
    else if  (diskFreeSize >= 1024)
      urlCacheDiskCapacity = 150 * MB;
    else 
      urlCacheDiskCapacity = 100 * MB;
    break ;
  }
  };
  return urlCacheDiskCapacity;
}

能够看到 Webkit的磁盘缓存巨细定在 100M ~ 1G之间。尽管内部有一个磁盘办理,但是关于大多数App来说,Webkit缓存超越300M也是不期望的。因而,天然想到是否能够经过某种方法来设置磁盘缓存的空间

WK_CLASS_AVAILABLE(macos(10.11), ios(9.0))
@interface WKWebsiteDataStore : NSObject <NSSecureCoding>
+ (WKWebsiteDataStore *)defaultDataStore;
+ (WKWebsiteDataStore *)nonPersistentDataStore;
- (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
@property (nonatomic, readonly, getter=isPersistent) BOOL persistent;
+ (NSSet<NSString *> *)allWebsiteDataTypes;
- (void)fetchDataRecordsOfTypes:(NSSet<NSString *> *)dataTypes completionHandler:(void (^)(NSArray<WKWebsiteDataRecord *> *))completionHandler WK_SWIFT_ASYNC_NAME(dataRecords(ofTypes:));
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler;
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;
@property (nonatomic, readonly) WKHTTPCookieStore *httpCookieStore WK_API_AVAILABLE(macos(10.13), ios(11.0));
@end

经过查看WKWebsiteDataStore供给的接口,并没有发现有关于设置磁盘缓存最大容量的相关接口。看来WKWebView并不期望咱们来自主控制Disk Cache的最大磁盘巨细。

但是其中找到了如下2个接口,是用来移除某个数据类型的缓存巨细。

- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler;
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;

前问有提到过,WKWebView的几种数据类型,包含如下:

  • Cookies
  • Disk and Memory caches
  • Persistent data such as WebSQL
  • IndexedDB databases
  • Local storage

现在Webkit占用的磁盘分为有几个目录,而且咱们经过线上80、90分位拉取到的数据,发现占用最大的两个目录分别为:

  • IndexedDB databases — appdata/Libraray/WebKit/WebsiteData/IndexedDB

  • Disk caches — appdata/Libraray/Caches/WebKit/NetworkCache

假如经过 removeDataOfTypes:modifiedSince:completionHandler接口来删去,modifiedSince是一个从某个时刻到现在时刻的disk cache,大部分时分,咱们想要删去的是那些之前的资源。

这个时分,咱们经过下面一些方法来做一些磁盘整理逻辑

IndexedDB databases的删去

该部分的删去能够直接经过读取文件价的创建时刻:appdata/Libraray/WebKit/WebsiteData/IndexedDB 例如该部分下面的数据为:https_google.game.hello,假如文件夹的创建或修正时刻大于某个阈值,咱们能够经过拿到https_google,再截取出域名google,然后经过fetchDataRecordsOfTypes:completionHandler获取一切的WKWebsiteDataTypeIndexedDBDatabases数据,根据每一个数据:WKWebsiteDataRecord的displayName判断是否包含域名来进行删去

NetworkCache的删去

WebKit的大部分缓存数据都是存储在NetworkCache里边,有用的办理NetworkCache,关于磁盘办理十分有帮助。
既然WebViedw供给了以下接口,咱们就能够合理的运用来达到删去的目的

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

笔者设计的方案是,对NetworkCache每次启动检测,由后台下发一个该目录的阈值,假如超越阈值,就会按照LRU的方法进行删去。

具体方法为:

  1. 首要获取appdata/Libraray/Caches/WebKit/NetworkCache/Version 16/Records下的一切文件
  2. 读取下面一切文件的创建或修正时刻,按修正时刻排序
  3. 按照40%的删去策略,咱们会对前40%的文件,修正文件的修正时刻为未来3天
  4. 接着运用removeDataOfTypes:modifiedSince:completionHandler来删去未来2天的数据

这样,即运用了WKWebsiteDataStore的体系删去接口,避免因为多线程操作文件引起的文件读写IO溃散,也对Webkit的NetworkCache进行了有用的办理

这里给咱们留个问题,appdata/Libraray/Caches/WebKit/NetworkCache/Version 16/下的目录包含了2个目录

  • Records

  • Blobs

为什么只删去一个Records,就能够了,而Blobs目录的清除作业,又谁来做?

咱们依然回归到源码


void Storage::clear(String&& type, WallTime modifiedSinceTime, CompletionHandler<void()>&& completionHandler)
{
    ASSERT(RunLoop::isMain());
    LOG(NetworkCacheStorage, "(NetworkProcess) clearing cache");
    if (m_recordFilter)
        m_recordFilter->clear();
    if (m_blobFilter)
        m_blobFilter->clear();
    m_approximateRecordsSize = 0;
    ioQueue().dispatch([this, protectedThis = Ref { *this }, modifiedSinceTime, completionHandler = WTFMove(completionHandler), type = WTFMove(type).isolatedCopy()] () mutable {
        auto recordsPath = this->recordsPathIsolatedCopy();
        traverseRecordsFiles(recordsPath, type, [modifiedSinceTime](const String& fileName, const String& hashString, const String& type, bool isBlob, const String& recordDirectoryPath) {
            auto filePath = FileSystem::pathByAppendingComponent(recordDirectoryPath, fileName);
            if (modifiedSinceTime > -WallTime::infinity()) {
                auto times = fileTimes(filePath);
                if (times.modification < modifiedSinceTime)
                    return;
            }
            FileSystem::deleteFile(filePath);
        });
        deleteEmptyRecordsDirectories(recordsPath);
        // This cleans unreferenced blobs.
        m_blobStorage.synchronize();
        RunLoop::main().dispatch(WTFMove(completionHandler));
    });
}

从源码中能够看到,体系的clear接口 只会首要去删去appdata/Libraray/Caches/WebKit/NetworkCache/Version 16/Records下的ModifySince之后的文件,然后经过m_blobStorage.synchronize()去删去没有hard-link的文件Blobs的文件,从而Blobs目录也被办理起来了。

期望咱们能够经过阅览Webkit的源码了解更多的Webkit知识,然后在公屏留言回复您的意见。

结语

Webkit的磁盘办理,只是咱们优化磁盘空间占用的一个方面,还有许多磁盘办理的策略,会在接下来的文章和咱们一起分享。

咱们也开源了一个动画库:github.com/yylive/yyev… ,期望咱们能够跳转改github和咱们交流,顺便点个star感谢咱们的支撑