You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为WKWebView中加载HTML字符串的功能实现URL请求缓存

改造HTML字符串加载为带缓存的URL请求方案

原代码通过loadHTMLString直接加载拼接后的HTML内容,无法利用WebKit的NSURLRequest缓存策略。要实现缓存功能,我们可以将生成的HTML内容封装为可缓存的HTTP响应,通过自定义虚拟URL和URLProtocol拦截请求,结合URLCache实现缓存逻辑。以下是具体改造方案:

核心思路

  1. 用虚拟HTTP URL标识播放器请求,通过自定义URLProtocol拦截该请求
  2. 根据参数生成唯一缓存Key,确保不同参数组合对应独立缓存
  3. 优先读取缓存内容,无缓存时生成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 16:20:38