Unlocking WKWebView Performance: A Deep Dive into WebKit Cache Mechanics
This article explains how WKWebView leverages various WebKit cache types—including disk, memory, and page caches—detailing their structures, sizing strategies, eviction policies, and the exact source‑code logic that governs HTTP caching, cleanup, and validation on iOS.
1. Introduction
Caching reduces redundant data transfer, alleviates network bottlenecks, lowers server load, and speeds up page rendering. WKWebView inherits WebKit’s caching mechanisms, and although its closed architecture limits deep native customization, understanding the underlying source code enables developers to optimize performance and user experience.
2. Cache Overview
2.1 Standard WebKit Cache Types
WebKit defines several cache categories: WKWebsiteDataTypeDiskCache – HTTP disk cache. WKWebsiteDataTypeOfflineWebApplicationCache – Offline web app cache. WKWebsiteDataTypeMemoryCache – HTTP memory cache. WKWebsiteDataTypeSessionStorage – Session‑scoped storage. WKWebsiteDataTypeLocalStorage – Persistent key‑value storage. WKWebsiteDataTypeCookies – Cookie storage. WKWebsiteDataTypeIndexedDBDatabases – IndexedDB. WKWebsiteDataTypeWebSQLDatabases – Legacy WebSQL.
Analysis of real device data shows that IndexedDB and NetworkCache dominate storage, accounting for over 80% of disk usage.
2.2 PageCache (Back/Forward Cache)
PageCache stores a full snapshot of a page in memory, allowing instant navigation without re‑loading resources or re‑rendering. Its capacity scales with available memory:
if (memorySize >= 512) backForwardCacheCapacity = 2;
else if (memorySize >= 256) backForwardCacheCapacity = 1;
else backForwardCacheCapacity = 0;Cached pages expire after 30 minutes via a timer:
static const Seconds expirationDelay { 30_min };
void WebBackForwardCacheEntry::expirationTimerFired() { /* cleanup logic */ }When the cache limit is exceeded, an LRU algorithm evicts the oldest entry:
void BackForwardCache::prune(PruningReason pruningReason) {
while (pageCount() > maxSize()) {
auto oldestItem = m_items.takeFirst();
oldestItem->setCachedPage(nullptr);
oldestItem->m_pruningReason = pruningReason;
}
}3. HTTP Memory Cache
3.1 Memory Cache Size Allocation
Memory cache size is chosen based on device RAM:
≥ 2 GB → 128 MB
1.5 GB – 2 GB → 96 MB
1 GB – 1.5 GB → 64 MB
0.5 GB – 1 GB → 32 MB
Other → 16 MB
if (memorySize >= 2048) cacheTotalCapacity = 128 * MB;
else if (memorySize >= 1536) cacheTotalCapacity = 96 * MB;
else if (memorySize >= 1024) cacheTotalCapacity = 64 * MB;
else if (memorySize >= 512) cacheTotalCapacity = 32 * MB;
else cacheTotalCapacity = 16 * MB;3.2 Memory Cache Operations
Resources are stored in a map keyed by URL and partition:
bool MemoryCache::add(CachedResource& resource) {
if (disabled() || resource.resourceRequest().httpMethod() != "GET") return false;
auto key = std::make_pair(resource.url(), resource.cachePartition());
ensureSessionResourceMap(resource.sessionID()).set(key, &resource);
resource.setInCache(true);
resourceAccessed(resource);
return true;
}Lookup follows the same key construction:
CachedResource* MemoryCache::resourceForRequest(const ResourceRequest& request, PAL::SessionID sessionID) {
auto* resources = sessionResourceMap(sessionID);
if (!resources) return nullptr;
return resourceForRequestImpl(request, *resources);
}
CachedResource* MemoryCache::resourceForRequestImpl(const ResourceRequest& request, CachedResourceMap& resources) {
URL url = removeFragmentIdentifierIfNeeded(request.url());
auto key = std::make_pair(url, request.cachePartition());
return resources.get(key);
}Reading strategy differs from disk caching; it uses a custom RevalidationPolicy enum:
enum RevalidationPolicy { Use, Revalidate, Reload, Load };
RevalidationPolicy policy = determineRevalidationPolicy(...);4. HTTP Disk Cache
4.1 Disk Cache Process
All network requests pass through the NetworkProcess, which applies standard HTTP cache rules (validation, freshness, etc.) before deciding to read, store, or bypass the cache.
4.2 Disk Cache Size Allocation
The default location is Library/Caches/WebKit/NetworkCache. Capacity adapts to free disk space:
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;4.3 Store Decision Logic
static StoreDecision makeStoreDecision(const ResourceRequest& req, const ResourceResponse& resp, size_t bodySize) {
if (!req.url().protocolIsInHTTPFamily() || !resp.isInHTTPFamily())
return StoreDecision::NoDueToProtocol;
if (req.httpMethod() != "GET")
return StoreDecision::NoDueToHTTPMethod;
auto directives = parseCacheControlDirectives(req.httpHeaderFields());
if (directives.noStore) return StoreDecision::NoDueToNoStoreRequest;
if (resp.cacheControlContainsNoStore()) return StoreDecision::NoDueToNoStoreResponse;
// Additional checks for status code, validator fields, etc.
return StoreDecision::Yes;
}4.4 Retrieve Decision Logic
static RetrieveDecision makeRetrieveDecision(const ResourceRequest& request) {
if (request.httpMethod() != "GET") return RetrieveDecision::NoDueToHTTPMethod;
if (request.cachePolicy() == ReloadIgnoringCacheData && !request.isConditional())
return RetrieveDecision::NoDueToReloadIgnoringCache;
return RetrieveDecision::Yes;
}4.5 Use Decision & Freshness Evaluation
When a cached entry is considered, the engine decides whether to use it directly, revalidate it, or fetch anew. Freshness is computed per RFC 7234:
Seconds computeFreshnessLifetimeForHTTPFamily(const ResourceResponse& resp, WallTime now) {
if (auto maxAge = resp.cacheControlMaxAge()) return *maxAge;
if (auto expires = resp.expires()) return *expires - (resp.date().valueOr(now));
// Heuristic fallback based on status code or Last‑Modified.
if (auto lastMod = resp.lastModified())
return (now - *lastMod) * 0.1;
return 0_us;
}The final use decision may trigger validation, async revalidation, or direct use:
static UseDecision makeUseDecision(NetworkProcess& netProc, const Entry& entry, const ResourceRequest& req) {
if (req.isConditional() && !entry.redirectRequest()) return UseDecision::Validate;
if (!verifyVaryingRequestHeaders(...)) return UseDecision::NoDueToVaryingHeaderMismatch;
if (cachePolicyAllowsExpired(req.cachePolicy())) return UseDecision::Use;
// Compute age and lifetime, compare with max‑stale, possibly revalidate.
return UseDecision::Validate;
}4.6 Cache Validation & Update
When a conditional request confirms that a cached response is still valid, only selected headers are refreshed:
void updateResponseHeadersAfterRevalidation(ResourceResponse& stored, const ResourceResponse& fresh) {
for (const auto& header : fresh.httpHeaderFields()) {
if (shouldUpdateHeaderAfterRevalidation(header.key))
stored.setHTTPHeaderField(header.key, header.value);
}
}5. Manual Cache Cleanup on iOS
In‑memory caches disappear when the process ends. Disk caches can be cleared manually:
NSString *libraryDir = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0];
NSString *bundleId = [[NSBundle mainBundle] infoDictionary][@"CFBundleIdentifier"];
NSString *webkitFolder = [NSString stringWithFormat:@"%@/WebKit", libraryDir];
NSString *cachesFolder = [NSString stringWithFormat:@"%@/Caches/%@/WebKit", libraryDir, bundleId];
[[NSFileManager defaultManager] removeItemAtPath:cachesFolder error:nil];
[[NSFileManager defaultManager] removeItemAtPath:webkitFolder error:nil];iOS 9+ provides an API to purge specific data types on the main thread:
NSSet *websiteDataTypes = [NSSet setWithArray:@[
WKWebsiteDataTypeDiskCache,
WKWebsiteDataTypeOfflineWebApplicationCache,
WKWebsiteDataTypeLocalStorage,
WKWebsiteDataTypeCookies,
WKWebsiteDataTypeSessionStorage,
WKWebsiteDataTypeIndexedDBDatabases,
WKWebsiteDataTypeWebSQLDatabases
]];
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{ }];6. Visual References
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
