手动下载并缓存资源是一种有效的方式,可以确保在需要时资源已经在本地存储,这样可以显著提高加载速度。
缓存整个 web 页面的所有资源文件
具体实现步骤
- 下载和缓存资源:包括 HTML 文件、CSS、JavaScript 和图像。
- 在应用启动时预加载资源。
- 构建包含所有预加载资源的 HTML 字符串。
- 加载构建的 HTML 字符串到
WKWebView
。
资源下载和缓存管理类
import Foundationclass 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 inguard 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 WebKitclass 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内容到webViewwebView.loadHTMLString(completeHTMLString, baseURL: nil)} else {// 如果没有缓存,则加载远程 URLlet request = URLRequest(url: URL(string: "https://www.example.com")!)webView.load(request)}}private func embedCachedResources(in htmlString: String) -> String {var modifiedHTMLString = htmlString// 嵌入预加载的CSSif 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)}// 嵌入预加载的JavaScriptif 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 WebKitclass ViewController: UIViewController {var webView: WKWebView!override func viewDidLoad() {super.viewDidLoad()// 创建并配置 WKWebViewlet 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 inif 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 inguard 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}
}