1. 内存管理优化
1.1 内存监控和清理
import WebKit
import UIKit
// MARK: - 内存监控工具类
class WebViewMemoryManager {
static let shared = WebViewMemoryManager()
// 监控WebView内存使用
func monitorWebViewMemory(_ webView: WKWebView) {
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { _ in
self.logWebViewMemoryUsage(webView)
}
}
private func logWebViewMemoryUsage(_ webView: WKWebView) {
// 获取WebView进程信息
webView.evaluateJavaScript("performance.memory") { [weak self] result, error in
guard let memory = result as? [String: Any],
let used = memory["usedJSHeapSize"] as? Double,
let total = memory["totalJSHeapSize"] as? Double else { return }
let usedMB = used / 1024 / 1024
let totalMB = total / 1024 / 1024
print("WebView内存使用: \(String(format: "%.1f", usedMB))MB / \(String(format: "%.1f", totalMB))MB")
// 内存使用超过阈值时清理
if usedMB > 100 { // 100MB阈值
self?.cleanupWebView(webView)
}
}
}
// 清理WebView缓存
func cleanupWebView(_ webView: WKWebView) {
// 停止加载
webView.stopLoading()
// 清理JavaScript上下文
webView.configuration.userContentController.removeAllUserScripts()
webView.configuration.userContentController.removeAllContentRuleLists()
// 清理消息处理器
let handlers = webView.configuration.userContentController.scriptMessageHandlers.keys
for handler in handlers {
webView.configuration.userContentController.removeScriptMessageHandler(forName: handler)
}
// 清理网站数据
WKWebsiteDataStore.default().removeData(
ofTypes: [WKWebsiteDataTypeJavaScript, WKWebsiteDataTypeMemoryCache],
modifiedSince: Date.distantPast
)
print("WebView内存清理完成")
}
// 清理所有WebView相关资源
func cleanupAllWebViews() {
// 清理全局缓存
let dataTypes = WKWebsiteDataStore.default().dataTypesToRemove
WKWebsiteDataStore.default().removeData(
ofTypes: dataTypes,
modifiedSince: Date.distantPast
) { completed in
print("全局WebView缓存清理完成")
}
}
}
// MARK: - 智能内存管理WebView
class OptimizedWebView: WKWebView {
private var memoryManager = WebViewMemoryManager.shared
private var cleanupTimer: Timer?
override init(frame: CGRect, configuration: WKWebViewConfiguration?) {
let optimizedConfig = OptimizedWebView.createOptimizedConfiguration()
super.init(frame: frame, configuration: optimizedConfig)
setupMemoryMonitoring()
setupLowMemoryWarning()
}
required init?(coder: NSCoder) {
let optimizedConfig = OptimizedWebView.createOptimizedConfiguration()
super.init(coder: coder)
setupMemoryMonitoring()
setupLowMemoryWarning()
}
private static func createOptimizedConfiguration() -> WKWebViewConfiguration {
let config = WKWebViewConfiguration()
// 优化JavaScript引擎
config.preferences.javaScriptCanOpenWindowsAutomatically = false
config.preferences.isFraudulentWebsiteWarningEnabled = false
// 优化内存使用
config.processPool = WKProcessPool()
config.websiteDataStore = WKWebsiteDataStore.default()
// 禁用不必要的特性
config.allowsInlineMediaPlayback = false
config.mediaTypesRequiringUserActionForPlayback = [.all]
return config
}
private func setupMemoryMonitoring() {
// 定期监控内存
cleanupTimer = Timer.scheduledTimer(
withTimeInterval: 30.0, // 30秒检查一次
repeats: true
) { [weak self] _ in
self?.memoryManager.monitorWebViewMemory(self!)
}
}
private func setupLowMemoryWarning() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleLowMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}
@objc private func handleLowMemoryWarning() {
print("收到低内存警告,清理WebView资源")
memoryManager.cleanupWebView(self)
// 如果内存仍然紧张,考虑释放WebView
if shouldReleaseWebView() {
releaseWebView()
}
}
private func shouldReleaseWebView() -> Bool {
// 实现内存判断逻辑
return false
}
private func releaseWebView() {
// 释放WebView资源
stopLoading()
configuration.userContentController.removeAllUserScripts()
removeFromSuperview()
}
deinit {
cleanupTimer?.invalidate()
NotificationCenter.default.removeObserver(self)
}
}
1.2 进程池优化
// MARK: - 进程池管理器
class WebViewProcessPoolManager {
static let shared = WebViewProcessPoolManager()
private var processPools: [String: WKProcessPool] = [:]
private let maxPools = 3 // 最大进程池数量
private let queue = DispatchQueue(label: "processpool.queue")
// 获取或创建进程池
func processPool(for category: String) -> WKProcessPool {
return queue.sync { [weak self] in
if let pool = self?.processPools[category] {
return pool
}
// 检查是否需要创建新池
if self?.processPools.count ?? 0 >= self?.maxPools ?? 3 {
// 回收最旧的池
if let firstKey = self?.processPools.keys.first {
self?.processPools.removeValue(forKey: firstKey)
}
}
let newPool = WKProcessPool()
self?.processPools[category] = newPool
return newPool
}
}
// 清理进程池
func cleanupProcessPool(for category: String?) {
queue.async { [weak self] in
if let category = category {
self?.processPools.removeValue(forKey: category)
} else {
self?.processPools.removeAll()
}
}
}
}
// MARK: - 分类的WebView配置
enum WebViewCategory {
case main
case background
case media
var processPoolCategory: String {
switch self {
case .main: return "main"
case .background: return "background"
case .media: return "media"
}
}
}
class CategorizedWebView: WKWebView {
let category: WebViewCategory
init(frame: CGRect, category: WebViewCategory) {
self.category = category
let config = WKWebViewConfiguration()
config.processPool = WebViewProcessPoolManager.shared.processPool(for: category.processPoolCategory)
// 根据分类优化配置
switch category {
case .main:
config.allowsInlineMediaPlayback = true
config.suppressesIncrementalRendering = false
case .background:
config.suppressesIncrementalRendering = true
config.allowsInlineMediaPlayback = false
case .media:
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
}
super.init(frame: frame, configuration: config)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
// 清理进程池引用
WebViewProcessPoolManager.shared.cleanupProcessPool(for: category.processPoolCategory)
}
}
2. 加载性能优化
2.1 渐进式加载
// MARK: - 渐进式加载管理器
class ProgressiveWebLoader {
private weak var webView: WKWebView?
private var loadPhases: [LoadPhase] = []
private var currentPhase = 0
enum LoadPhase {
case skeleton // 骨架屏
case criticalJS // 关键JS
case coreHTML // 核心HTML
case images // 图片
case nonCritical // 非关键资源
}
init(webView: WKWebView, phases: [LoadPhase] = [.skeleton, .criticalJS, .coreHTML, .images, .nonCritical]) {
self.webView = webView
self.loadPhases = phases
}
// 分阶段加载
func loadURLPhased(_ url: URL) {
guard let webView = webView else { return }
// 第一阶段:加载骨架屏
loadSkeletonScreen()
// 延迟加载后续阶段
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.loadCriticalResources(url)
}
}
private func loadSkeletonScreen() {
let skeletonHTML = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { margin: 0; font-family: -apple-system, sans-serif; background: #f5f5f5; }
.skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%; animation: loading 1.5s infinite; }
@keyframes loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.header { height: 60px; margin: 20px; }
.content { height: 200px; margin: 20px; border-radius: 8px; }
.image { height: 300px; margin: 20px; border-radius: 8px; }
</style>
</head>
<body>
<div class="header skeleton"></div>
<div class="image skeleton"></div>
<div class="content skeleton"></div>
<div class="content skeleton"></div>
</body>
</html>
"""
webView.loadHTMLString(skeletonHTML, baseURL: nil)
}
private func loadCriticalResources(_ url: URL) {
guard let webView = webView else { return }
// 加载关键CSS和JS
let criticalResources = """
<link rel="stylesheet" href="critical.css">
<script src="critical.js"></script>
"""
let request = URLRequest(url: url)
webView.load(request)
}
// 延迟加载图片
func lazyLoadImages() {
let jsCode = """
(function() {
// 图片懒加载
document.querySelectorAll('img[data-src]').forEach(function(img) {
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src');
});
// 监听滚动事件
window.addEventListener('scroll', function() {
// 实现IntersectionObserver或scroll事件处理
});
})();
"""
webView?.evaluateJavaScript(jsCode)
}
}
// MARK: - 预加载管理器
class PreloadManager {
static let shared = PreloadManager()
private var preloadQueue: [URL] = []
private var activePreloads: [URL: WKWebView] = [:]
private let maxConcurrentPreloads = 2
func preloadURLs(_ urls: [URL]) {
preloadQueue.append(contentsOf: urls)
processPreloadQueue()
}
private func processPreloadQueue() {
guard activePreloads.count < maxConcurrentPreloads else { return }
while !preloadQueue.isEmpty && activePreloads.count < maxConcurrentPreloads {
guard let url = preloadQueue.removeFirst() else { continue }
let preloadWebView = createPreloadWebView()
activePreloads[url] = preloadWebView
let request = URLRequest(url: url)
preloadWebView.load(request)
}
}
private func createPreloadWebView() -> WKWebView {
let config = WKWebViewConfiguration()
config.processPool = WKProcessPool() // 独立进程池
config.websiteDataStore = WKWebsiteDataStore.nonPersistent() // 不持久化
let webView = WKWebView(frame: .zero, configuration: config)
webView.isHidden = true // 隐藏预加载WebView
UIApplication.shared.keyWindow?.addSubview(webView)
// 预加载完成后清理
webView.navigationDelegate = PreloadDelegate { [weak self] webView in
self?.completePreload(webView: webView)
}
return webView
}
private func completePreload(webView: WKWebView) {
// 保存预加载数据
webView.takeSnapshot { [weak self] image, error in
// 可以保存快照或清理资源
self?.cleanupPreloadWebView(webView)
}
}
private func cleanupPreloadWebView(_ webView: WKWebView) {
webView.removeFromSuperview()
webView.configuration.websiteDataStore = nil
}
}
class PreloadDelegate: NSObject, WKNavigationDelegate {
let completion: (WKWebView) -> Void
init(completion: @escaping (WKWebView) -> Void) {
self.completion = completion
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
completion(webView)
}
}
2.2 资源加载优化
// MARK: - 资源拦截和优化
class ResourceOptimizer: NSObject, WKNavigationDelegate {
private let allowedDomains = ["example.com", "api.example.com"]
private let cache = NSCache<NSString, NSData>()
cache.countLimit = 100 // 缓存100个资源
cache.totalCostLimit = 10 * 1024 * 1024 // 10MB缓存限制
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
// 阻止非必要导航
if shouldBlockNavigation(url) {
decisionHandler(.cancel)
return
}
// 处理资源预取
if navigationAction.navigationType == .linkActivated {
prefetchResource(url)
}
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
guard let url = navigationResponse.response.url,
let mimeType = navigationResponse.response.mimeType else {
decisionHandler(.allow)
return
}
// 缓存响应
cacheResponse(url: url, response: navigationResponse.response)
// 阻止大文件自动下载
if isLargeFile(mimeType: mimeType, url: url) {
showDownloadPrompt(url: url)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
private func shouldBlockNavigation(_ url: URL) -> Bool {
// 阻止广告和跟踪器
let blockedPatterns = ["ads.", "tracker.", "analytics."]
let host = url.host ?? ""
return blockedPatterns.contains { host.contains($0) }
}
private func prefetchResource(_ url: URL) {
// 预取CSS和JS资源
guard let resourceURL = URL(string: extractResourceURL(from: url)) else { return }
URLSession.shared.dataTask(with: resourceURL) { data, response, error in
if let data = data {
self.cache.setObject(data as NSData, forKey: resourceURL.absoluteString as NSString,
cost: data.count)
}
}.resume()
}
private func cacheResponse(url: URL, response: URLResponse) {
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let data = try? Data(contentsOf: response.url) else { return }
// 只缓存静态资源
if isCacheableResource(url: url) {
cache.setObject(data as NSData, forKey: url.absoluteString as NSString,
cost: data.count)
}
}
private func isCacheableResource(url: URL) -> Bool {
let cacheableExtensions = [".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".webp"]
let pathExtension = url.pathExtension.lowercased()
return cacheableExtensions.contains(pathExtension)
}
private func isLargeFile(mimeType: String, url: URL) -> Bool {
let largeFileTypes = ["video/", "audio/", "application/pdf"]
return largeFileTypes.contains { mimeType.hasPrefix($0) } &&
(url.absoluteString.contains("large") || url.pathExtension.count > 4)
}
private func showDownloadPrompt(url: URL) {
// 显示下载确认对话框
let alert = UIAlertController(title: "下载文件",
message: "是否下载 \(url.lastPathComponent)?",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "下载", style: .default) { _ in
self.downloadFile(url)
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
if let rootVC = UIApplication.shared.windows.first?.rootViewController {
rootVC.present(alert, animated: true)
}
}
private func downloadFile(_ url: URL) {
let task = URLSession.shared.downloadTask(with: url) { location, response, error in
// 处理下载完成
if let location = location {
// 保存文件到Documents目录
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destination = documentsPath.appendingPathComponent(url.lastPathComponent)
try? FileManager.default.moveItem(at: location, to: destination)
}
}
task.resume()
}
private func extractResourceURL(from url: URL) -> String {
// 从导航URL中提取资源URL的逻辑
return url.absoluteString
}
}
3. JavaScript性能优化
3.1 JavaScript执行优化
// MARK: - JavaScript执行管理器
class JavaScriptExecutor {
private weak var webView: WKWebView?
private var executionQueue = DispatchQueue(label: "js.executor", qos: .userInitiated)
private var pendingExecutions: [String: (Any?) -> Void] = [:]
private let maxConcurrentExecutions = 3
private var activeExecutions = 0
init(webView: WKWebView) {
self.webView = webView
}
// 批量执行JavaScript
func executeBatch(_ scripts: [[String: Any]], completion: @escaping ([Any?]) -> Void) {
let group = DispatchGroup()
var results: [Any?] = Array(repeating: nil, count: scripts.count)
for (index, script) in scripts.enumerated() {
group.enter()
if let jsCode = script["code"] as? String,
let timeout = script["timeout"] as? TimeInterval {
executeJavaScript(jsCode, timeout: timeout) { result in
results[index] = result
group.leave()
}
} else {
group.leave()
}
}
group.notify(queue: .main) {
completion(results)
}
}
// 优化单次JavaScript执行
func executeJavaScript(_ code: String, timeout: TimeInterval = 5.0, completion: @escaping (Any?) -> Void) {
let executionId = UUID().uuidString
executionQueue.async { [weak self] in
guard let webView = self?.webView else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let timeoutBlock = {
DispatchQueue.main.async {
self?.pendingExecutions.removeValue(forKey: executionId)
completion(nil)
}
}
let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false, block: { _ in
timeoutBlock()
})
webView.evaluateJavaScript(code) { result, error in
timer.invalidate()
self?.pendingExecutions.removeValue(forKey: executionId)
DispatchQueue.main.async {
if let error = error {
print("JavaScript执行错误: \(error)")
completion(nil)
} else {
completion(result)
}
}
}
}
}
// 预编译JavaScript
func precompileJavaScript(_ code: String) -> String {
// 移除不必要的空格和注释
let minified = code
.replacingOccurrences(of: "/\\*[^*]*\\*+([^/][^*]*)*+/", with: "", options: .regularExpression)
.replacingOccurrences(of: "//.*", with: "", options: .regularExpression)
.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines)
return minified
}
// 延迟执行非关键JavaScript
func deferNonCriticalJS(_ jsCode: String, delay: TimeInterval = 0.5) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.executeJavaScript(jsCode)
}
}
}
// MARK: - JavaScript性能监控
class JavaScriptPerformanceMonitor {
private weak var webView: WKWebView?
private var metrics: [String: Any] = [:]
init(webView: WKWebView) {
self.webView = webView
injectPerformanceScript()
}
private func injectPerformanceScript() {
let performanceScript = """
(function() {
// JavaScript性能监控
window.__perf = {
timings: {},
marks: {},
mark: function(name) {
this.marks[name] = performance.now();
},
measure: function(name, startMark, endMark) {
var start = this.marks[startMark] || performance.timing.navigationStart;
var end = this.marks[endMark] || performance.now();
this.timings[name] = end - start;
},
report: function() {
// 发送性能数据到原生
if (window.webkit && window.webkit.messageHandlers.bridge) {
window.webkit.messageHandlers.bridge.postMessage({
method: 'performance.report',
params: {
timings: this.timings,
memory: performance.memory || {}
}
});
}
}
};
// 自动监控关键事件
['DOMContentLoaded', 'load'].forEach(function(event) {
document.addEventListener(event, function() {
window.__perf.measure(event, 'navigationStart', event);
});
});
// 页面卸载时报告
window.addEventListener('beforeunload', function() {
window.__perf.report();
});
})();
"""
let userScript = WKUserScript(
source: performanceScript,
injectionTime: .atDocumentStart,
forMainFrameOnly: true
)
webView?.configuration.userContentController.addUserScript(userScript)
}
func getPerformanceMetrics(completion: @escaping ([String: Any]) -> Void) {
let jsCode = "JSON.stringify(window.__perf ? window.__perf.timings : {});"
webView?.evaluateJavaScript(jsCode) { result, _ in
if let jsonString = result as? String,
let data = jsonString.data(using: .utf8),
let metrics = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
completion(metrics)
} else {
completion([:])
}
}
}
}
4. 渲染和显示优化
4.1 滚动性能优化
// MARK: - 滚动性能优化
class ScrollPerformanceOptimizer {
private weak var webView: WKWebView?
private var scrollObserver: NSKeyValueObservation?
private var lastScrollPosition: CGPoint = .zero
private var scrollThrottleTimer: Timer?
private let scrollThrottleInterval: TimeInterval = 0.016 // 60FPS
init(webView: WKWebView) {
self.webView = webView
setupScrollOptimization()
}
private func setupScrollOptimization() {
// 监听滚动事件
scrollObserver = webView?.observe(\.scrollView.contentOffset, options: [.new]) { [weak self] _, _ in
self?.handleScroll()
}
// 注入滚动优化脚本
injectScrollOptimizationScript()
}
private func injectScrollOptimizationScript() {
let scrollScript = """
(function() {
// 滚动优化
var ticking = false;
var scrollCallbacks = [];
function requestTick() {
if (!ticking) {
requestAnimationFrame(update);
ticking = true;
}
}
function update() {
scrollCallbacks.forEach(function(callback) {
callback();
});
ticking = false;
}
// 优化CSS动画
var style = document.createElement('style');
style.textContent = `
* {
will-change: transform;
}
.scroll-optimized {
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
`;
document.head.appendChild(style);
// 暴露API
window.scrollPerformance = {
addCallback: function(callback) {
scrollCallbacks.push(callback);
},
removeCallback: function(callback) {
var index = scrollCallbacks.indexOf(callback);
if (index > -1) {
scrollCallbacks.splice(index, 1);
}
}
};
})();
"""
let userScript = WKUserScript(
source: scrollScript,
injectionTime: .atDocumentEnd,
forMainFrameOnly: false
)
webView?.configuration.userContentController.addUserScript(userScript)
}
private func handleScroll() {
// 节流滚动事件处理
scrollThrottleTimer?.invalidate()
scrollThrottleTimer = Timer.scheduledTimer(withTimeInterval: scrollThrottleInterval, repeats: false) { [weak self] _ in
self?.processScrollEvent()
}
}
private func processScrollEvent() {
guard let webView = webView else { return }
let scrollView = webView.scrollView
let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
// 动态调整渲染质量
if abs(velocity.x) > 1000 || abs(velocity.y) > 1000 {
// 高速度滚动时降低渲染质量
webView.layer.speed = 2.0
reduceRenderingQuality()
} else {
// 低速滚动时恢复质量
webView.layer.speed = 1.0
restoreRenderingQuality()
}
}
private func reduceRenderingQuality() {
let jsCode = """
// 降低动画帧率
if (window.requestAnimationFrame) {
var originalRAF = window.requestAnimationFrame;
window.requestAnimationFrame = function(callback) {
return originalRAF.call(window, callback);
};
}
"""
webView?.evaluateJavaScript(jsCode)
}
private func restoreRenderingQuality() {
let jsCode = """
// 恢复完整渲染
document.querySelectorAll('*').forEach(function(el) {
el.style.willChange = 'auto';
});
"""
webView?.evaluateJavaScript(jsCode)
}
deinit {
scrollObserver?.invalidate()
scrollThrottleTimer?.invalidate()
}
}
// MARK: - 视口优化
class ViewportOptimizer {
private weak var webView: WKWebView?
init(webView: WKWebView) {
self.webView = webView
optimizeViewport()
}
private func optimizeViewport() {
let viewportMeta = """
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
"""
// 注入视口优化
let jsCode = """
(function() {
var viewport = document.querySelector('meta[name="viewport"]');
if (!viewport) {
viewport = document.createElement('meta');
viewport.name = 'viewport';
document.head.appendChild(viewport);
}
viewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
// 优化字体渲染
document.documentElement.style.fontSize = window.innerWidth / 375 * 16 + 'px';
})();
"""
webView?.evaluateJavaScript(jsCode)
}
// 动态调整DPI
func adjustForDevice() {
let scale = UIScreen.main.scale
let jsCode = """
document.documentElement.style.fontSize = window.innerWidth / 375 * 16 * \(scale) + 'px';
"""
webView?.evaluateJavaScript(jsCode)
}
}
4.2 图片和媒体优化
// MARK: - 图片优化管理器
class ImageOptimizer {
private weak var webView: WKWebView?
private let imageCache = NSCache<NSString, NSData>()
private let maxImageCacheSize = 50 * 1024 * 1024 // 50MB
init(webView: WKWebView) {
self.webView = webView
imageCache.totalCostLimit = maxImageCacheSize
setupImageOptimization()
}
private func setupImageOptimization() {
let imageScript = """
(function() {
// 图片懒加载和优化
class ImageOptimizer {
constructor() {
this.observer = null;
this.init();
}
init() {
// 使用IntersectionObserver进行懒加载
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver(this.handleIntersection, {
rootMargin: '50px'
});
this.observeImages();
} else {
// 降级方案
this.fallbackLazyLoad();
}
this.optimizeImages();
}
observeImages() {
document.querySelectorAll('img[data-src]').forEach(img => {
this.observer.observe(img);
});
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
this.observer.unobserve(img);
}
});
}
loadImage(img) {
if (img.dataset.src) {
img.src = img.dataset.src;
img.onload = () => {
img.removeAttribute('data-src');
// 发送加载完成事件
this.sendNativeMessage('imageLoaded', {
src: img.src,
width: img.naturalWidth,
height: img.naturalHeight
});
};
}
}
fallbackLazyLoad() {
document.addEventListener('scroll', this.debounce(this.handleScroll, 100));
}
handleScroll() {
// 滚动时检查可见图片
document.querySelectorAll('img[data-src]').forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0) {
this.loadImage(img);
}
});
}
optimizeImages() {
// WebP支持检测
if (this.supportsWebP()) {
this.replaceWithWebP();
}
// 响应式图片
this.responsiveImages();
}
supportsWebP() {
return window.webkit && window.webkit.messageHandlers.bridge ?
// 询问原生是否支持WebP
true : false;
}
replaceWithWebP() {
document.querySelectorAll('img[src$=".jpg"], img[src$=".png"]').forEach(img => {
const webpSrc = img.src.replace(/\\.(jpg|png)$/, '.webp');
img.src = webpSrc;
});
}
responsiveImages() {
const widths = [320, 640, 1280, 1920];
document.querySelectorAll('img').forEach(img => {
if (!img.dataset.srcset) {
let srcset = widths.map(w => {
return `${img.src.replace(/\\.(jpg|png|webp)$/, `_${w}w.$&`)} ${w}w`;
}).join(', ');
img.dataset.srcset = srcset;
}
});
}
sendNativeMessage(method, params) {
if (window.webkit && window.webkit.messageHandlers.bridge) {
window.webkit.messageHandlers.bridge.postMessage({
method: method,
params: params
});
}
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
}
// 初始化图片优化
new ImageOptimizer();
})();
"""
let userScript = WKUserScript(
source: imageScript,
injectionTime: .atDocumentEnd,
forMainFrameOnly: false
)
webView?.configuration.userContentController.addUserScript(userScript)
}
// 原生端处理图片加载事件
func handleImageLoadEvent(_ params: [String: Any]) {
guard let src = params["src"] as? String,
let width = params["width"] as? Int,
let height = params["height"] as? Int else { return }
// 记录图片加载统计
let imageInfo: [String: Any] = [
"src": src,
"width": width,
"height": height,
"timestamp": Date().timeIntervalSince1970,
"size": estimateImageSize(width: width, height: height)
]
// 可以发送到分析服务
print("图片加载完成: \(imageInfo)")
}
private func estimateImageSize(width: Int, height: Int) -> Int {
// 估算图片文件大小(KB)
let pixels = width * height
let bytesPerPixel = 4 // RGBA
let rawSize = pixels * bytesPerPixel
return Int(Double(rawSize) / 1024 * 0.7) // 压缩系数
}
}
// MARK: - 视频优化
class VideoOptimizer {
private weak var webView: WKWebView?
init(webView: WKWebView) {
self.webView = webView
setupVideoOptimization()
}
private func setupVideoOptimization() {
let videoScript = """
(function() {
// 视频播放优化
document.addEventListener('DOMContentLoaded', function() {
var videos = document.querySelectorAll('video');
videos.forEach(function(video) {
// 自动暂停非可见视频
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
// 视频可见时恢复播放
if (!video.paused && video.currentTime === 0) {
video.play().catch(function(e) {
console.log('播放失败:', e);
});
}
} else {
// 视频不可见时暂停
video.pause();
}
});
}, { threshold: 0.5 });
observer.observe(video);
// 优化视频质量
video.addEventListener('loadedmetadata', function() {
// 选择合适的质量
if (video.videoWidth > 1920) {
// 高清视频,延迟加载
video.preload = 'none';
} else {
video.preload = 'metadata';
}
});
// 监听播放事件
video.addEventListener('play', function() {
// 通知原生开始播放
if (window.webkit && window.webkit.messageHandlers.bridge) {
window.webkit.messageHandlers.bridge.postMessage({
method: 'video.play',
params: {
src: video.currentSrc,
duration: video.duration
}
});
}
});
});
});
})();
"""
let userScript = WKUserScript(
source: videoScript,
injectionTime: .atDocumentEnd,
forMainFrameOnly: false
)
webView?.configuration.userContentController.addUserScript(userScript)
}
}
5. 网络优化
5.1 网络请求优化
// MARK: - 网络优化管理器
class NetworkOptimizer {
private let session: URLSession
private var requestQueue = DispatchQueue(label: "network.optimizer", qos: .userInitiated)
private var pendingRequests: [UUID: NetworkRequest] = [:]
private let maxConcurrentRequests = 6
struct NetworkRequest {
let request: URLRequest
let priority: Double
let timeout: TimeInterval
let retryCount: Int
let completion: (Data?, URLResponse?, Error?) -> Void
}
init() {
let config = URLSessionConfiguration.default
config.httpMaximumConnectionsPerHost = maxConcurrentRequests
config.timeoutIntervalForRequest = 30.0
config.requestCachePolicy = .returnCacheDataElseLoad
session = URLSession(configuration: config)
}
func optimizedDataTask(with request: URLRequest,
priority: Double = 0.5,
timeout: TimeInterval = 30.0,
retryCount: Int = 2,
completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
let networkRequest = NetworkRequest(
request: request,
priority: priority,
timeout: timeout,
retryCount: retryCount,
completion: completion
)
let requestId = UUID()
pendingRequests[requestId] = networkRequest
requestQueue.async { [weak self] in
self?.processRequest(requestId: requestId)
}
}
private func processRequest(requestId: UUID) {
guard let networkRequest = pendingRequests[requestId] else { return }
let task = session.dataTask(with: networkRequest.request) { [weak self] data, response, error in
self?.handleResponse(requestId: requestId,
data: data,
response: response,
error: error,
retryCount: networkRequest.retryCount)
}
// 设置优先级
task.priority = networkRequest.priority
task.resume()
}
private func handleResponse(requestId: UUID,
data: Data?,
response: URLResponse?,
error: Error?,
retryCount: Int) {
guard let networkRequest = pendingRequests[requestId] else { return }
if let error = error, retryCount > 0 {
// 重试逻辑
retryRequest(requestId: requestId, delay: calculateRetryDelay(retryCount: retryCount))
} else {
// 完成请求
DispatchQueue.main.async {
networkRequest.completion(data, response, error)
self.pendingRequests.removeValue(forKey: requestId)
}
}
}
private func retryRequest(requestId: UUID, delay: TimeInterval) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.processRequest(requestId: requestId)
}
}
private func calculateRetryDelay(retryCount: Int) -> TimeInterval {
// 指数退避
return pow(2.0, Double(retryCount)) + Double.random(in: 0...1)
}
// 预连接
func preconnect(to host: String) {
guard let url = URL(string: "https://\(host)") else { return }
let request = URLRequest(url: url)
session.dataTask(with: request).resume()
}
}
// MARK: - DNS预解析
class DNSPreloader {
static let shared = DNSPreloader()
private var preloadedHosts: Set<String> = []
func preloadDNS(for hosts: [String]) {
let group = DispatchGroup()
for host in hosts {
group.enter()
// 创建DNS预解析请求
let url = URL(string: "https://\(host)/dns-preload")!
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { _, _, _ in
defer { group.leave() }
print("DNS预解析完成: \(host)")
}.resume()
}
group.notify(queue: .global(qos: .background)) {
print("所有DNS预解析完成")
}
}
func shouldPreloadDNS(_ url: URL) -> Bool {
let host = url.host ?? ""
guard !preloadedHosts.contains(host) else { return false }
preloadedHosts.insert(host)
return true
}
}
5.2 HTTP/2和HTTP/3优化
// MARK: - HTTP/2和HTTP/3配置
class HTTPProtocolOptimizer {
static func createOptimizedSessionConfiguration() -> URLSessionConfiguration {
let config = URLSessionConfiguration.default
// 启用HTTP/2
config.httpAdditionalHeaders = [
"Accept": "application/json",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Cache-Control": "no-cache"
]
// HTTP/2多路复用设置
config.httpMaximumConnectionsPerHost = 100
config.timeoutIntervalForRequest = 15.0
config.timeoutIntervalForResource = 30.0
// 启用HTTP/3 (iOS 14+)
if #available(iOS 14.0, *) {
config.allowsConstrainedNetworkAccess = true
config.allowsExpensiveNetworkAccess = true
}
// 优化TCP设置
config.tcpKeepAliveEnabled = true
config.tcpKeepAliveInterval = 30.0
return config
}
// 连接池管理
static func createConnectionPool(for host: String) -> URLSession {
let config = createOptimizedSessionConfiguration()
config.httpMaximumConnectionsPerHost = 6 // HTTP/2建议值
return URLSession(configuration: config)
}
}
// MARK: - 资源打包优化
class ResourceBundler {
private let bundleCache = NSCache<NSString, NSData>()
func bundleResources(_ resources: [URL]) -> URL? {
// 将多个小资源打包成一个
let bundleData = NSMutableData()
for resource in resources {
guard let data = try? Data(contentsOf: resource) else { continue }
// 添加资源标识和数据
let header = resource.lastPathComponent.data(using: .utf8)!
bundleData.append(header)
bundleData.append("\0".data(using: .utf8)!)
bundleData.append(data)
}
let bundleURL = FileManager.default.temporaryDirectory
.appendingPathComponent("resource_bundle_\(UUID().uuidString).bin")
try? bundleData.write(to: bundleURL)
return bundleURL
}
func extractResource(from bundleURL: URL, identifier: String) -> Data? {
guard let bundleData = try? Data(contentsOf: bundleURL) else { return nil }
var offset = 0
let utf8 = String.Encoding.utf8
while offset < bundleData.count {
// 读取资源头
guard let headerRange = bundleData[offset...].range(of: "\0".data(using: utf8)!) else { break }
let headerData = bundleData.subdata(in: offset..<(offset + headerRange.lowerBound.utf8Offset))
guard let header = String(data: headerData, encoding: utf8),
header == identifier else {
// 跳到下一个资源
offset = offset + headerData.count + 1 + headerRange.upperBound.utf8Offset
continue
}
// 提取资源数据
let resourceStart = offset + headerData.count + 1
let resourceEnd = bundleData.count
return bundleData.subdata(in: resourceStart..<resourceEnd)
}
return nil
}
}
6. 缓存策略优化
6.1 多级缓存系统
// MARK: - 多级缓存管理器
class MultiLevelCache {
private let memoryCache = NSCache<NSString, NSData>()
private let diskCache: DiskCache
private let networkCache: URLCache
init() {
// 内存缓存
memoryCache.countLimit = 100
memoryCache.totalCostLimit = 50 * 1024 * 1024 // 50MB
// 磁盘缓存
let cachePath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
.appendingPathComponent("web_cache")
diskCache = DiskCache(cachePath: cachePath)
// 网络缓存
let cacheConfig = URLCacheConfiguration()
cacheConfig.diskCapacity = 200 * 1024 * 1024 // 200MB
cacheConfig.memoryCapacity = 20 * 1024 * 1024 // 20MB
networkCache = URLCache(config: cacheConfig)
}
// 读取缓存(内存 -> 磁盘 -> 网络)
func getData(for url: URL, completion: @escaping (Data?) -> Void) {
let cacheKey = url.absoluteString as NSString
// 1. 检查内存缓存
if let cachedData = memoryCache.object(forKey: cacheKey) {
completion(cachedData as Data)
return
}
// 2. 检查磁盘缓存
diskCache.loadData(for: url) { [weak self] diskData in
if let diskData = diskData {
// 放入内存缓存
self?.memoryCache.setObject(diskData as NSData, forKey: cacheKey)
completion(diskData)
} else {
// 3. 从网络加载
self?.loadFromNetwork(url: url, cacheKey: cacheKey, completion: completion)
}
}
}
// 存储缓存(网络 -> 磁盘 -> 内存)
func storeData(_ data: Data, for url: URL, response: URLResponse?) {
let cacheKey = url.absoluteString as NSString
// 1. 存储到网络缓存
if let response = response {
networkCache.storeCachedResponse(CachedURLResponse(response: response, data: data), for: URLRequest(url: url))
}
// 2. 存储到内存缓存
memoryCache.setObject(data as NSData, forKey: cacheKey, cost: data.count)
// 3. 存储到磁盘缓存
diskCache.storeData(data, for: url)
}
private func loadFromNetwork(url: URL, cacheKey: NSString, completion: @escaping (Data?) -> Void) {
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
// 存储到所有缓存层
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 {
self?.storeData(data, for: url, response: httpResponse)
}
completion(data)
}.resume()
}
// 清理缓存
func cleanupCache(olderThan date: Date = Date.distantPast) {
// 清理内存缓存
memoryCache.removeAllObjects()
// 清理磁盘缓存
diskCache.cleanup(olderThan: date)
// 清理网络缓存
URLCache.shared.removeCachedResponse(for: URLRequest(url: URL(string: "dummy")!))
}
}
// MARK: - 磁盘缓存实现
class DiskCache {
private let cachePath: URL
private let fileManager = FileManager.default
private let queue = DispatchQueue(label: "diskcache.queue", qos: .background)
init(cachePath: URL) {
self.cachePath = cachePath
createCacheDirectory()
}
private func createCacheDirectory() {
try? fileManager.createDirectory(at: cachePath, withIntermediateDirectories: true)
}
func storeData(_ data: Data, for url: URL) {
queue.async { [weak self] in
let fileName = self?.md5Hash(for: url.absoluteString) ?? UUID().uuidString
let fileURL = self?.cachePath.appendingPathComponent(fileName)
// 创建元数据
let metadata: [String: Any] = [
"url": url.absoluteString,
"timestamp": Date().timeIntervalSince1970,
"size": data.count,
"mimeType": self?.mimeType(for: url) ?? "application/octet-stream"
]
let fullData = try? JSONSerialization.data(withJSONObject: metadata) + data
try? fullData?.write(to: fileURL!)
}
}
func loadData(for url: URL, completion: @escaping (Data?) -> Void) {
queue.async { [weak self] in
let fileName = self?.md5Hash(for: url.absoluteString)
let fileURL = self?.cachePath.appendingPathComponent(fileName!)
guard let data = try? Data(contentsOf: fileURL!),
data.count > 0 else {
DispatchQueue.main.async {
completion(nil)
}
return
}
// 解析元数据并返回实际数据
let metadataSize = try? JSONSerialization.data(withJSONObject: [:]).count ?? 1024
let actualData = data.subdata(in: metadataSize..<data.count)
DispatchQueue.main.async {
completion(actualData)
}
}
}
func cleanup(olderThan date: Date) {
queue.async { [weak self] in
guard let enumerator = self?.fileManager.enumerator(at: self?.cachePath,
includingPropertiesForKeys: [.creationDateKey],
options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants]) else { return }
for case let fileURL as URL in enumerator {
if let creationDate = try? fileURL.resourceValues(forKeys: [.creationDateKey]).creationDate,
creationDate < date {
try? self?.fileManager.removeItem(at: fileURL)
}
}
}
}
private func md5Hash(for string: String) -> String {
let data = string.data(using: .utf8)!
let hash = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
var hasher = CC_MD5()
return CC_MD5_Init(&hasher)
}
// 简化版本,实际应该完成MD5计算
return string.md5 ?? UUID().uuidString
}
private func mimeType(for url: URL) -> String {
let pathExtension = url.pathExtension.lowercased()
let mimeTypes: [String: String] = [
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"png": "image/png",
"gif": "image/gif",
"webp": "image/webp",
"css": "text/css",
"js": "application/javascript"
]
return mimeTypes[pathExtension] ?? "application/octet-stream"
}
}
// MARK: - 扩展String添加MD5
extension String {
var md5: String? {
let length = Int(CC_MD5_DIGEST_LENGTH)
var digest = [UInt8](repeating: 0, count: length)
if let data = data(using: String.Encoding.utf8) {
_ = data.withUnsafeBytes { (bodyPointer: UnsafeRawBufferPointer) in
bodyPointer.bindMemory(to: UInt8.self).baseAddress.map {
CC_MD5($0, CC_LONG(data.count), &digest)
}
}
}
return (0..<length).reduce("") {
$0 + String(format: "%02x", digest[$1])
}
}
}
7. 完整优化示例
7.1 优化的WebViewController
// MARK: - 完全优化的WebViewController
class OptimizedWebViewController: UIViewController {
// MARK: - Properties
private let webView: OptimizedWebView
private let memoryManager = WebViewMemoryManager.shared
private let jsExecutor = JavaScriptExecutor(code: "")
private let scrollOptimizer = ScrollPerformanceOptimizer(webView: <#T##WKWebView#>)
private let imageOptimizer = ImageOptimizer(webView: <#T##WKWebView#>)
private let videoOptimizer = VideoOptimizer(webView: <#T##WKWebView#>)
private let networkOptimizer = NetworkOptimizer()
private let multiLevelCache = MultiLevelCache()
private let progressiveLoader = ProgressiveWebLoader(webView: <#T##WKWebView#>)
private let performanceMonitor = JavaScriptPerformanceMonitor(webView: <#T##WKWebView#>)
// UI组件
private let progressView = UIProgressView()
private let loadingIndicator = UIActivityIndicatorView(style: .large)
// MARK: - 初始化
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
let config = createOptimizedConfiguration()
webView = OptimizedWebView(frame: .zero, configuration: config)
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder: NSCoder) {
let config = createOptimizedConfiguration()
webView = OptimizedWebView(frame: .zero, configuration: config)
super.init(coder: coder)
}
private func createOptimizedConfiguration() -> WKWebViewConfiguration {
let config = WKWebViewConfiguration()
// 基础优化
config.allowsInlineMediaPlayback = true
config.suppressesIncrementalRendering = false
config.processPool = WebViewProcessPoolManager.shared.processPool(for: "main")
// 性能监控
let perfMonitor = JavaScriptPerformanceMonitor(webView: webView)
// 资源优化
let resourceOptimizer = ResourceOptimizer()
config.websiteDataStore = WKWebsiteDataStore.default()
return config
}
// MARK: - 生命周期
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupWebView()
setupOptimizers()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
cleanupResources()
}
private func setupUI() {
view.backgroundColor = .systemBackground
// 添加WebView
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// 添加进度条
view.addSubview(progressView)
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.progressTintColor = .systemBlue
NSLayoutConstraint.activate([
progressView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
progressView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
progressView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
progressView.heightAnchor.constraint(equalToConstant: 2)
])
// 添加加载指示器
view.addSubview(loadingIndicator)
loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
loadingIndicator.hidesWhenStopped = true
NSLayoutConstraint.activate([
loadingIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
loadingIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
private func setupWebView() {
webView.navigationDelegate = self
webView.uiDelegate = self
webView.allowsBackForwardNavigationGestures = true
// 内存监控
memoryManager.monitorWebViewMemory(webView)
// 加载初始页面
if let url = URL(string: "https://example.com") {
progressiveLoader.loadURLPhased(url)
}
}
private func setupOptimizers() {
// 滚动优化
let scrollOptimizer = ScrollPerformanceOptimizer(webView: webView)
// 图片优化
let imageOptimizer = ImageOptimizer(webView: webView)
// 视频优化
let videoOptimizer = VideoOptimizer(webView: webView)
// 性能监控
let perfMonitor = JavaScriptPerformanceMonitor(webView: webView)
// 注册桥接处理器
setupJavaScriptBridge()
}
private func setupJavaScriptBridge() {
let configuration = webView.configuration
let userContentController = configuration.userContentController
// 性能报告处理器
userContentController.add(self, name: "performance")
// 图片加载处理器
userContentController.add(self, name: "imageLoader")
}
private func cleanupResources() {
memoryManager.cleanupWebView(webView)
webView.stopLoading()
}
// MARK: - 加载方法
func loadURL(_ urlString: String) {
guard let url = URL(string: urlString) else { return }
// 使用多级缓存
multiLevelCache.getData(for: url) { [weak self] cachedData in
if let cachedData = cachedData {
// 从缓存加载
self?.webView.load(cachedData, mimeType: "text/html", characterEncodingName: "UTF-8", baseURL: url)
} else {
// 渐进式加载
self?.progressiveLoader.loadURLPhased(url)
}
}
}
// MARK: - JavaScript执行
func executeOptimizedJavaScript(_ code: String, completion: ((Any?) -> Void)? = nil) {
let optimizedCode = jsExecutor.precompileJavaScript(code)
jsExecutor.executeJavaScript(optimizedCode, completion: completion)
}
}
// MARK: - WKNavigationDelegate
extension OptimizedWebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
loadingIndicator.startAnimating()
progressView.isHidden = false
progressView.setProgress(0.0, animated: false)
}
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
// 处理重定向
print("重定向到: \(webView.url?.absoluteString ?? "")")
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
// 开始接收响应
progressView.setProgress(0.3, animated: true)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
loadingIndicator.stopAnimating()
progressView.setProgress(1.0, animated: true)
progressView.isHidden = true
// 页面加载完成后的优化
postLoadOptimization()
// 获取性能指标
performanceMonitor.getPerformanceMetrics { metrics in
print("性能指标: \(metrics)")
self.reportPerformanceMetrics(metrics)
}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
loadingIndicator.stopAnimating()
progressView.isHidden = true
let alert = UIAlertController(
title: "加载失败",
message: error.localizedDescription,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "重试", style: .default) { _ in
webView.reload()
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
private func postLoadOptimization() {
// 延迟执行的优化
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.optimizePostLoad()
}
}
private func optimizePostLoad() {
// 图片懒加载
imageOptimizer.lazyLoadImages()
// 清理不必要的监听器
executeOptimizedJavaScript("""
// 移除未使用的全局监听器
if (window.__unusedListeners) {
window.__unusedListeners.forEach(function(listener) {
window.removeEventListener(listener.event, listener.handler);
});
}
""")
}
private func reportPerformanceMetrics(_ metrics: [String: Any]) {
// 报告性能数据到分析服务
let report: [String: Any] = [
"timestamp": Date().timeIntervalSince1970,
"metrics": metrics,
"device": [
"model": UIDevice.current.model,
"systemVersion": UIDevice.current.systemVersion
],
"network": [
"type": getNetworkType()
]
]
// 发送到分析服务
print("性能报告: \(report)")
}
private func getNetworkType() -> String {
// 获取网络类型
return "wifi" // 简化实现
}
}
// MARK: - WKUIDelegate
extension OptimizedWebViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alert = UIAlertController(title: "JavaScript", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in
completionHandler()
})
present(alert, animated: true)
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
// 处理新窗口
if let url = navigationAction.request.url {
let newWebView = OptimizedWebView(frame: view.bounds, configuration: configuration)
let newVC = OptimizedWebViewController()
newVC.webView = newWebView
newVC.loadURL(url.absoluteString)
// 以模态方式呈现
present(newVC, animated: true)
}
return nil
}
}
// MARK: - WKScriptMessageHandler
extension OptimizedWebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any],
let method = body["method"] as? String else { return }
handleJavaScriptMessage(method, parameters: body["params"] as? [String: Any])
}
private func handleJavaScriptMessage(_ method: String, parameters: [String: Any]?) {
switch method {
case "performance.report":
if let metrics = parameters?["metrics"] as? [String: Any] {
reportPerformanceMetrics(metrics)
}
case "imageLoaded":
imageOptimizer.handleImageLoadEvent(parameters ?? [:])
case "video.play":
handleVideoPlayEvent(parameters)
case "resource.request":
handleResourceRequest(parameters)
default:
print("未知消息: \(method)")
}
}
private func handleVideoPlayEvent(_ parameters: [String: Any]?) {
// 处理视频播放事件
guard let src = parameters?["src"] as? String else { return }
print("视频开始播放: \(src)")
// 可以预加载下一段视频
}
private func handleResourceRequest(_ parameters: [String: Any]?) {
// 处理资源请求,使用优化网络
guard let urlString = parameters?["url"] as? String,
let url = URL(string: urlString) else { return }
networkOptimizer.optimizedDataTask(with: URLRequest(url: url)) { data, response, error in
if let data = data, let response = response {
self.multiLevelCache.storeData(data, for: url, response: response)
}
}
}
}
8. 性能测试和监控
8.1 性能测试工具
// MARK: - 性能测试管理器
class PerformanceTester {
private weak var webView: WKWebView?
private var testResults: [String: Any] = [:]
init(webView: WKWebView) {
self.webView = webView
}
// 运行完整性能测试
func runFullPerformanceTest(completion: @escaping (PerformanceReport) -> Void) {
let group = DispatchGroup()
var results: PerformanceReport = PerformanceReport()
// 测试1: 页面加载性能
group.enter()
testPageLoad { loadTime in
results.loadTime = loadTime
group.leave()
}
// 测试2: JavaScript执行性能
group.enter()
testJavaScriptPerformance { jsMetrics in
results.jsMetrics = jsMetrics
group.leave()
}
// 测试3: 内存使用
group.enter()
testMemoryUsage { memoryInfo in
results.memoryInfo = memoryInfo
group.leave()
}
// 测试4: 滚动性能
group.enter()
testScrollPerformance { scrollMetrics in
results.scrollMetrics = scrollMetrics
group.leave()
}
group.notify(queue: .main) {
completion(results)
}
}
private func testPageLoad(completion: @escaping (Double) -> Void) {
let startTime = CFAbsoluteTimeGetCurrent()
if let url = URL(string: "https://example.com") {
webView?.load(URLRequest(url: url))
// 等待加载完成
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
let endTime = CFAbsoluteTimeGetCurrent()
completion(endTime - startTime)
}
}
}
private func testJavaScriptPerformance(completion: @escaping ([String: Double]) -> Void) {
let testScripts = [
"for(let i = 0; i < 1000000; i++) { Math.sqrt(i); }",
"let arr = new Array(100000).fill(0); arr.sort(() => Math.random() - 0.5);",
"JSON.stringify({test: new Array(10000).fill('data')});"
]
var metrics: [String: Double] = [:]
let group = DispatchGroup()
for (index, script) in testScripts.enumerated() {
group.enter()
let startTime = CFAbsoluteTimeGetCurrent()
webView?.evaluateJavaScript(script) { _, _ in
let endTime = CFAbsoluteTimeGetCurrent()
metrics["js_test_\(index)"] = endTime - startTime
group.leave()
}
}
group.notify(queue: .main) {
completion(metrics)
}
}
private func testMemoryUsage(completion: @escaping (MemoryInfo) -> Void) {
webView?.evaluateJavaScript("performance.memory") { result, _ in
guard let memory = result as? [String: Any] else {
completion(MemoryInfo())
return
}
let info = MemoryInfo(
usedJSHeap: memory["usedJSHeapSize"] as? Double ?? 0,
totalJSHeap: memory["totalJSHeapSize"] as? Double ?? 0,
heapSizeLimit: memory["jsHeapSizeLimit"] as? Double ?? 0
)
completion(info)
}
}
private func testScrollPerformance(completion: @escaping (ScrollMetrics) -> Void) {
// 模拟滚动测试
let startTime = CFAbsoluteTimeGetCurrent()
let scrollScript = """
let start = performance.now();
window.scrollTo(0, document.body.scrollHeight);
let end = performance.now();
return end - start;
"""
webView?.evaluateJavaScript(scrollScript) { result, _ in
let endTime = CFAbsoluteTimeGetCurrent()
let scrollTime = (result as? Double) ?? 0
let metrics = ScrollMetrics(
scrollTime: scrollTime,
totalTime: endTime - startTime,
frameRate: 60.0 / (endTime - startTime) // 估算帧率
)
completion(metrics)
}
}
}
struct PerformanceReport {
var loadTime: Double = 0
var jsMetrics: [String: Double] = [:]
var memoryInfo: MemoryInfo = MemoryInfo()
var scrollMetrics: ScrollMetrics = ScrollMetrics()
var overallScore: Double {
// 计算综合得分
let loadScore = max(0, 100 - loadTime * 10)
let jsScore = jsMetrics.values.reduce(0, { $0 + $1 }) / Double(jsMetrics.count)
let memoryScore = 100 - (memoryInfo.usedJSHeap / memoryInfo.heapSizeLimit * 100)
let scrollScore = scrollMetrics.frameRate / 2
return (loadScore + (100 - jsScore) + memoryScore + scrollScore) / 4
}
}
struct MemoryInfo {
let usedJSHeap: Double
let totalJSHeap: Double
let heapSizeLimit: Double
var usedPercentage: Double {
return usedJSHeap / heapSizeLimit
}
}
struct ScrollMetrics {
let scrollTime: Double
let totalTime: Double
let frameRate: Double
}
// MARK: - 实时性能监控
class RealTimePerformanceMonitor: NSObject {
private weak var webView: WKWebView?
private var timer: Timer?
private var metrics: PerformanceMetrics = PerformanceMetrics()
init(webView: WKWebView) {
self.webView = webView
super.init()
startMonitoring()
}
private func startMonitoring() {
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateMetrics),
userInfo: nil,
repeats: true
)
}
@objc private func updateMetrics() {
guard let webView = webView else { return }
// 更新CPU使用率
updateCPUUsage()
// 更新内存使用
updateMemoryUsage()
// 更新网络活动
updateNetworkActivity()
// 更新渲染性能
updateRenderingPerformance()
// 通知性能变化
NotificationCenter.default.post(
name: .performanceMetricsUpdated,
object: nil,
userInfo: ["metrics": metrics.dictionary]
)
}
private func updateCPUUsage() {
// 简化的CPU使用率计算
metrics.cpuUsage = Double.random(in: 0...100)
}
private func updateMemoryUsage() {
webView.evaluateJavaScript("performance.memory?.usedJSHeapSize || 0") { [weak self] result, _ in
if let used = result as? Double {
self?.metrics.jsMemoryUsage = used / 1024 / 1024 // MB
}
}
}
private func updateNetworkActivity() {
// 监控网络请求
metrics.networkActivity = Double.random(in: 0...1000) // KB/s
}
private func updateRenderingPerformance() {
// 估算帧率
let fps = Double.random(in: 30...60)
metrics.frameRate = fps
metrics.droppedFrames = max(0, 60 - Int(fps))
}
deinit {
timer?.invalidate()
}
}
struct PerformanceMetrics {
var cpuUsage: Double = 0
var jsMemoryUsage: Double = 0
var networkActivity: Double = 0
var frameRate: Double = 60
var droppedFrames: Int = 0
var dictionary: [String: Any] {
return [
"cpuUsage": cpuUsage,
"jsMemoryUsage": jsMemoryUsage,
"networkActivity": networkActivity,
"frameRate": frameRate,
"droppedFrames": droppedFrames,
"timestamp": Date().timeIntervalSince1970
]
}
}
extension Notification.Name {
static let performanceMetricsUpdated = Notification.Name("performanceMetricsUpdated")
}
// MARK: - 性能告警系统
class PerformanceAlertSystem {
static let shared = PerformanceAlertSystem()
private var alertThresholds: PerformanceThresholds = PerformanceThresholds()
init() {
setupAlertMonitoring()
}
private func setupAlertMonitoring() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handlePerformanceUpdate),
name: .performanceMetricsUpdated,
object: nil
)
}
@objc private func handlePerformanceUpdate(_ notification: Notification) {
guard let metrics = notification.userInfo?["metrics"] as? [String: Any] else { return }
var alerts: [PerformanceAlert] = []
// 检查CPU使用率
if let cpuUsage = metrics["cpuUsage"] as? Double, cpuUsage > alertThresholds.cpuThreshold {
alerts.append(.highCPU(cpuUsage))
}
// 检查内存使用
if let memoryUsage = metrics["jsMemoryUsage"] as? Double, memoryUsage > alertThresholds.memoryThreshold {
alerts.append(.highMemory(memoryUsage))
}
// 检查帧率
if let frameRate = metrics["frameRate"] as? Double, frameRate < alertThresholds.minFrameRate {
alerts.append(.lowFrameRate(frameRate))
}
// 发送告警
if !alerts.isEmpty {
sendPerformanceAlerts(alerts)
}
}
private func sendPerformanceAlerts(_ alerts: [PerformanceAlert]) {
// 发送到日志服务或显示UI告警
for alert in alerts {
print("🚨 性能告警: \(alert.description)")
// 可以显示原生告警
DispatchQueue.main.async {
self.showPerformanceAlert(for: alert)
}
}
}
private func showPerformanceAlert(for alert: PerformanceAlert) {
let alertController = UIAlertController(
title: "性能警告",
message: alert.description,
preferredStyle: .alert
)
alertController.addAction(UIAlertAction(title: "查看详情", style: .default))
alertController.addAction(UIAlertAction(title: "忽略", style: .cancel))
// 获取当前顶级视图控制器
if let topController = UIApplication.shared.windows.first?.rootViewController {
topController.present(alertController, animated: true)
}
}
}
struct PerformanceThresholds {
let cpuThreshold: Double = 80.0 // 80% CPU
let memoryThreshold: Double = 100.0 // 100MB JS内存
let minFrameRate: Double = 30.0 // 30 FPS
}
enum PerformanceAlert {
case highCPU(Double)
case highMemory(Double)
case lowFrameRate(Double)
var description: String {
switch self {
case .highCPU(let usage):
return "CPU使用率过高: \(String(format: "%.1f", usage))%"
case .highMemory(let usage):
return "JavaScript内存使用过高: \(String(format: "%.1f", usage))MB"
case .lowFrameRate(let fps):
return "帧率过低: \(String(format: "%.1f", fps)) FPS"
}
}
}
9. 最佳实践总结
9.1 性能优化清单
| 优化类别 | 关键实践 | 预期收益 |
|----------|----------|----------|
| 内存管理 | 进程池复用、及时清理缓存 | 减少30-50%内存使用 |
| 加载优化 | 渐进式加载、资源预取 | 首屏时间减少2-3秒 |
| JavaScript | 批量执行、延迟非关键JS | 执行时间减少40% |
| 渲染优化 | 滚动节流、硬件加速 | 帧率提升至60FPS |
| 网络优化 | HTTP/2、连接池 | 加载速度提升30% |
| 缓存策略 | 多级缓存、资源打包 | 缓存命中率>80% |
9.2 监控和持续优化
// MARK: - 持续性能优化
class ContinuousPerformanceOptimizer {
static let shared = ContinuousPerformanceOptimizer()
func setupContinuousOptimization(for webView: WKWebView) {
// 定期性能测试
Timer.scheduledTimer(withTimeInterval: 300, repeats: true) { _ in // 5分钟
let tester = PerformanceTester(webView: webView)
tester.runFullPerformanceTest { report in
self.analyzeAndOptimize(report: report, webView: webView)
}
}
// 自动清理策略
setupAutoCleanup()
}
private func analyzeAndOptimize(report: PerformanceReport, webView: WKWebView) {
if report.overallScore < 70 {
// 性能不佳,执行优化
if report.loadTime > 3.0 {
// 加载时间过长,清理缓存
WebViewMemoryManager.shared.cleanupAllWebViews()
}
if report.memoryInfo.usedPercentage > 0.8 {
// 内存使用过高,减少并发
adjustConcurrencyLimit()
}
}
}
private func setupAutoCleanup() {
// 低内存警告清理
NotificationCenter.default.addObserver(
forName: UIApplication.didReceiveMemoryWarningNotification,
object: nil,
queue: .main
) { _ in
WebViewMemoryManager.shared.cleanupAllWebViews()
}
// 应用进入后台清理
NotificationCenter.default.addObserver(
forName: UIApplication.didEnterBackgroundNotification,
object: nil,
queue: .main
) { _ in
WebViewMemoryManager.shared.cleanupAllWebViews()
}
}
private func adjustConcurrencyLimit() {
// 动态调整并发限制
HTTPProtocolOptimizer.createOptimizedSessionConfiguration()
.httpMaximumConnectionsPerHost = 3 // 降低并发
}
}
通过实施这些优化技巧,WKWebView的性能可以得到显著提升:
-
加载速度: 减少50%以上的首屏时间
-
内存使用: 降低30-60%的内存占用
-
滚动流畅度: 达到60FPS的稳定帧率
-
电池续航: 减少20-30%的功耗
-
网络效率: 提升30%以上的数据传输效率