布景
越来越多的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的方法进行删去。
具体方法为:
- 首要获取appdata/Libraray/Caches/WebKit/NetworkCache/Version 16/Records下的一切文件
- 读取下面一切文件的创建或修正时刻,按修正时刻排序
- 按照40%的删去策略,咱们会对前40%的文件,修正文件的修正时刻为未来3天
- 接着运用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感谢咱们的支撑