手动下载并缓存资源是一种有效的方式,可以确保在需要时资源已经在本地存储,这样可以显著提高加载速度。
缓存整个 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
}
}