
手动下载并缓存资源是一种有效的方式,可以确保在需要时资源已经在本地存储,这样可以显著提高加载速度。
缓存整个 web 页面的所有资源文件
具体实现步骤
- 下载和缓存资源:包括 HTML 文件、CSS、JavaScript 和图像。
- 在应用启动时预加载资源。
- 构建包含所有预加载资源的 HTML 字符串。
- 加载构建的 HTML 字符串到 WKWebView。
资源下载和缓存管理类
import Foundation
class ResourceDownloader {
    static let shared = ResourceDownloader()
    private init() {}
    
    func downloadResources() {
        let resources = [
            URL(string: "https://www.example.com/styles.css")!,
            URL(string: "https://www.example.com/script.js")!,
            URL(string: "https://www.example.com/image.png")!,
            URL(string: "https://www.example.com/index.html")!
        ]
        
        for resource in resources {
            downloadResource(from: resource)
        }
    }
    
    private func downloadResource(from url: URL) {
        let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
            guard let localURL = localURL else { return }
            
            do {
                let data = try Data(contentsOf: localURL)
                self.cacheResource(data: data, url: url)
            } catch {
                print("Failed to load resource: \(error)")
            }
        }
        task.resume()
    }
    
    private func cacheResource(data: Data, url: URL) {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        do {
            try data.write(to: fileURL)
            print("Resource cached: \(fileURL)")
        } catch {
            print("Failed to cache resource: \(error)")
        }
    }
    
    func getCachedResource(for url: URL) -> Data? {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        return try? Data(contentsOf: fileURL)
    }
}
在应用启动时预加载资源
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 预加载资源
        ResourceDownloader.shared.downloadResources()
        return true
    }
}
使用缓存的资源加载完整页面
import UIKit
import WebKit
class ViewController: UIViewController {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        webView = WKWebView(frame: self.view.bounds)
        self.view.addSubview(webView)
        
        // 构建完整的HTML内容
        if let htmlURL = URL(string: "https://www.example.com/index.html"),
           let cachedHTML = ResourceDownloader.shared.getCachedResource(for: htmlURL),
           let htmlString = String(data: cachedHTML, encoding: .utf8) {
            
            let completeHTMLString = embedCachedResources(in: htmlString)
            
            // 加载HTML内容到webView
            webView.loadHTMLString(completeHTMLString, baseURL: nil)
        } else {
            // 如果没有缓存,则加载远程 URL
            let request = URLRequest(url: URL(string: "https://www.example.com")!)
            webView.load(request)
        }
    }
    
    private func embedCachedResources(in htmlString: String) -> String {
        var modifiedHTMLString = htmlString
        
        // 嵌入预加载的CSS
        if let cssURL = URL(string: "https://www.example.com/styles.css"),
           let cachedCSS = ResourceDownloader.shared.getCachedResource(for: cssURL),
           let cssString = String(data: cachedCSS, encoding: .utf8) {
            let cssTag = "<style>\(cssString)</style>"
            modifiedHTMLString = modifiedHTMLString.replacingOccurrences(of: "<link rel=\"stylesheet\" href=\"styles.css\">", with: cssTag)
        }
        
        // 嵌入预加载的JavaScript
        if let jsURL = URL(string: "https://www.example.com/script.js"),
           let cachedJS = ResourceDownloader.shared.getCachedResource(for: jsURL),
           let jsString = String(data: cachedJS, encoding: .utf8) {
            let jsTag = "<script>\(jsString)</script>"
            modifiedHTMLString = modifiedHTMLString.replacingOccurrences(of: "<script src=\"script.js\" defer></script>", with: jsTag)
        }
        
        // 嵌入预加载的图像
        if let imageURL = URL(string: "https://www.example.com/image.png"),
           let cachedImage = ResourceDownloader.shared.getCachedResource(for: imageURL) {
            let base64Image = cachedImage.base64EncodedString()
            let imgTag = "<img src='data:image/png;base64,\(base64Image)' alt='Preloaded Image'>"
            modifiedHTMLString = modifiedHTMLString.replacingOccurrences(of: "<img src=\"image.png\" alt=\"Preloaded Image\">", with: imgTag)
        }
        
        return modifiedHTMLString
    }
}
详细说明
-  资源下载和缓存: - 使用 URLSession下载资源(包括HTML文件、CSS、JavaScript和图像),并将其缓存到本地存储。
- 下载完成后,资源数据被写入本地存储,以便后续使用。
 
- 使用 
-  预加载资源: - 在应用启动时调用 downloadResources()方法,预先下载和缓存所需的资源。
 
- 在应用启动时调用 
-  使用缓存的资源加载完整页面: - 在 viewDidLoad()方法中,通过getCachedResource(for:)获取缓存的HTML内容。
- 使用 embedCachedResources(in:)方法,将预加载的 CSS、JavaScript 和图像嵌入到 HTML 内容中。
- 使用 webView.loadHTMLString(completeHTMLString, baseURL: nil)加载修改后的 HTML 内容。
 
- 在 
通过这种方式,可以确保在加载页面时直接使用本地缓存的资源,从而显著提高页面加载速度,提供更好的用户体验。
只缓存 CSS、JavaScript 和图像
只有 CSS、JavaScript 和图像是预加载的,而 HTML 文件是在打开 WKWebView 时才开始下载的。这种策略在某些情况下可能更加高效,尤其是当 HTML 文件需要动态生成或者频繁更新时。
为什么预加载 CSS、JavaScript 和图像?
预加载 CSS、JavaScript 和图像有几个优点:
- 减少首次渲染时间:通过提前加载关键资源,可以显著减少页面首次渲染的时间,提高用户体验。
- 减轻服务器压力:本地缓存的资源可以减少重复请求,减轻服务器的负载。
- 提高离线体验:在某些情况下,即使没有网络连接,用户也能看到页面的一部分内容。
什么时候 HTML 文件在打开 WKWebView 时下载?
- 动态内容:如果 HTML 文件内容是动态生成的(例如,包含个性化数据或实时更新的数据),那么在每次加载页面时请求最新的 HTML 文件是必要的。
- 频繁更新:如果 HTML 文件内容频繁更新,预加载 HTML 文件可能会导致内容不一致。
- 用户特定数据:某些应用需要根据用户的身份或状态生成不同的 HTML 内容。
完整的 Swift 代码实现
展示如何预加载 CSS、JavaScript 和图像,并在打开 WKWebView 时下载 HTML 文件。
import UIKit
import WebKit
class ViewController: UIViewController {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 创建并配置 WKWebView
        let configuration = WKWebViewConfiguration()
        let urlSchemeHandler = LocalResourceHandler()
        configuration.setURLSchemeHandler(urlSchemeHandler, forURLScheme: "local")
        
        webView = WKWebView(frame: self.view.bounds, configuration: configuration)
        self.view.addSubview(webView)
        
        // 加载远程 HTML 文件
        if let htmlURL = URL(string: "https://www.example.com/index.html") {
            let request = URLRequest(url: htmlURL)
            webView.load(request)
        }
    }
}
class LocalResourceHandler: NSObject, WKURLSchemeHandler {
    
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        guard let url = urlSchemeTask.request.url else {
            return
        }
        
        // 仅处理特定的 URL 方案,例如 "local"
        guard url.scheme == "local" else {
            // 对于非 "local" 方案的请求,不处理
            return
        }
        
        if let data = ResourceDownloader.shared.getCachedResource(for: url) {
            let mimeType = determineMimeType(for: url)
            let response = URLResponse(url: url, mimeType: mimeType, expectedContentLength: data.count, textEncodingName: nil)
            urlSchemeTask.didReceive(response)
            urlSchemeTask.didReceive(data)
            urlSchemeTask.didFinish()
        } else {
            // 缓存资源不可用,发起网络请求
            downloadResource(from: url, urlSchemeTask: urlSchemeTask)
        }
    }
    
    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {}
    
    private func determineMimeType(for url: URL) -> String {
        switch url.pathExtension {
        case "css":
            return "text/css"
        case "js":
            return "application/javascript"
        case "png":
            return "image/png"
        default:
            return "text/plain"
        }
    }
    
    private func downloadResource(from url: URL, urlSchemeTask: WKURLSchemeTask) {
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                urlSchemeTask.didFailWithError(error)
                return
            }
            
            guard let data = data, let response = response else {
                urlSchemeTask.didFailWithError(NSError(domain: "LocalResourceHandler", code: 404, userInfo: nil))
                return
            }
            
            // 缓存资源
            ResourceDownloader.shared.cacheResource(data: data, url: url)
            
            // 返回资源
            urlSchemeTask.didReceive(response)
            urlSchemeTask.didReceive(data)
            urlSchemeTask.didFinish()
        }
        task.resume()
    }
}
class ResourceDownloader {
    static let shared = ResourceDownloader()
    private init() {}
    
    func downloadResources() {
        let resources = [
            URL(string: "https://www.example.com/styles.css")!,
            URL(string: "https://www.example.com/script.js")!,
            URL(string: "https://www.example.com/image.png")!
        ]
        
        for resource in resources {
            downloadResource(from: resource)
        }
    }
    
    private func downloadResource(from url: URL) {
        let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
            guard let localURL = localURL else { return }
            
            do {
                let data = try Data(contentsOf: localURL)
                self.cacheResource(data: data, url: url)
            } catch {
                print("Failed to load resource: \(error)")
            }
        }
        task.resume()
    }
    
    func cacheResource(data: Data, url: URL) {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        do {
            try data.write(to: fileURL)
            print("Resource cached: \(fileURL)")
        } catch {
            print("Failed to cache resource: \(error)")
        }
    }
    
    func getCachedResource(for url: URL) -> Data? {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        return try? Data(contentsOf: fileURL)
    }
}
在应用启动时预加载资源
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 预加载资源
        ResourceDownloader.shared.downloadResources()
        return true
    }
}



















