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.

Baidu App Technology
Baidu App Technology
Baidu App Technology
Unlocking WKWebView Performance: A Deep Dive into WebKit Cache Mechanics

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

LocalStorage file example
LocalStorage file example
Process diagram of WKWebView caching
Process diagram of WKWebView caching
CacheiOSWebKitHTTPWKWebViewMemoryCacheDiskCache
Baidu App Technology
Written by

Baidu App Technology

Official Baidu App Tech Account

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.