如何为WKWebView中加载HTML字符串的功能实现URL请求缓存
改造HTML字符串加载为带缓存的URL请求方案
原代码通过loadHTMLString直接加载拼接后的HTML内容,无法利用WebKit的NSURLRequest缓存策略。要实现缓存功能,我们可以将生成的HTML内容封装为可缓存的HTTP响应,通过自定义虚拟URL和URLProtocol拦截请求,结合URLCache实现缓存逻辑。以下是具体改造方案:
核心思路
- 用虚拟HTTP URL标识播放器请求,通过自定义
URLProtocol拦截该请求 - 根据参数生成唯一缓存Key,确保不同参数组合对应独立缓存
- 优先读取缓存内容,无缓存时生成HTML并写入缓存,再通过
loadRequest加载
完整代码实现
1. 自定义URLProtocol拦截请求
这个类负责拦截我们定义的虚拟URL请求,从缓存中取出HTML内容并返回给WebView:
class YouTubePlayerCacheProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { // 仅拦截指定的虚拟播放器URL return request.url?.absoluteString == "https://youtube-player.local/custom-player" } override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } override func startLoading() { guard let cachedResponse = URLCache.shared.cachedResponse(for: request) else { // 无缓存时返回错误,实际场景可补充重新生成逻辑 client?.urlProtocol(self, didFailWithError: NSError(domain: "PlayerCache", code: -1, userInfo: [NSLocalizedDescriptionKey: "无缓存内容"])) return } // 返回缓存的响应数据 client?.urlProtocol(self, didReceive: cachedResponse.response, cacheStoragePolicy: .allowed) client?.urlProtocol(self, didLoad: cachedResponse.data) client?.urlProtocolDidFinishLoading(self) } override func stopLoading() {} }
2. 注册URLProtocol(App启动时执行)
在AppDelegate或SceneDelegate的启动方法中注册自定义协议,让系统能拦截指定请求:
URLProtocol.registerClass(YouTubePlayerCacheProtocol.self)
3. 改造后的加载函数
替换原有的loadWebViewWithParameters函数,实现缓存逻辑:
fileprivate func loadWebViewWithParameters(_ parameters: YouTubePlayerParameters) { let playerURL = URL(string: "https://youtube-player.local/custom-player")! // 基于参数生成唯一缓存Key(用参数JSON的Base64值作为标识) guard let jsonParameters = serializedJSON(parameters as AnyObject), let cacheKey = jsonParameters.data(using: .utf8)?.base64EncodedString() else { // 参数序列化失败,降级到原加载逻辑 fallbackToOriginalLoad(parameters) return } // 创建带缓存策略的请求,自定义Header存储缓存Key var request = URLRequest(url: playerURL, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 60) request.setValue(cacheKey, forHTTPHeaderField: "X-Player-Cache-Key") // 检查缓存是否存在 if URLCache.shared.cachedResponse(for: request) != nil { webView.load(request) return } // 无缓存时生成HTML字符串 guard let rawHTMLString = htmlStringWithFilePath(playerHTMLPath()), let htmlString = rawHTMLString.replacingOccurrences(of: "%@", with: jsonParameters) as String?, let htmlData = htmlString.data(using: .utf8) else { fallbackToOriginalLoad(parameters) return } // 创建自定义HTTP响应并写入缓存 let response = HTTPURLResponse(url: playerURL, statusCode: 200, HTTPVersion: "1.1", headerFields: ["Content-Type": "text/html; charset=utf-8"]) guard let validResponse = response else { fallbackToOriginalLoad(parameters) return } let cachedResponse = CachedURLResponse(response: validResponse, data: htmlData) URLCache.shared.storeCachedResponse(cachedResponse, for: request) // 加载请求 webView.load(request) } // 降级到原加载逻辑的辅助函数 fileprivate func fallbackToOriginalLoad(_ parameters: YouTubePlayerParameters) { guard let rawHTMLString = htmlStringWithFilePath(playerHTMLPath()), let jsonParameters = serializedJSON(parameters as AnyObject), let htmlString = rawHTMLString.replacingOccurrences(of: "%@", with: jsonParameters) as String? else { return } webView.loadHTMLString(htmlString, baseURL: URL(string: baseURL)) }
方案说明
- 缓存策略使用
returnCacheDataElseLoad,优先读取缓存,无缓存时生成内容并写入缓存 - 虚拟URL用于统一标识播放器请求,避免与真实网络请求冲突
- 保留原加载逻辑作为降级方案,确保参数序列化失败时仍能正常加载播放器
内容的提问来源于stack exchange,提问作者adalovelacy
相关产品推荐
相关产品推荐

