From f50ad505ae271897a1ab860e81ef442845a08d1f Mon Sep 17 00:00:00 2001 From: Ilyas Hallak Date: Tue, 30 Sep 2025 23:20:00 +0200 Subject: [PATCH] fix: Add memory leak prevention and proper WebView cleanup - Add dismantleUIView method to properly cleanup WebView resources - Remove script message handlers to prevent memory leaks - Add cleanup() method to WebViewCoordinator with timer invalidation - Clear all callbacks and references when view is destroyed - Add isCleanedUp guard to prevent double cleanup - Improve memory management for better stability --- readeck/UI/Components/WebView.swift | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/readeck/UI/Components/WebView.swift b/readeck/UI/Components/WebView.swift index 22cd835..a19fbbb 100644 --- a/readeck/UI/Components/WebView.swift +++ b/readeck/UI/Components/WebView.swift @@ -284,6 +284,29 @@ struct WebView: UIViewRepresentable { webView.loadHTMLString(styledHTML, baseURL: nil) } + // CRITICAL: Proper cleanup when view is destroyed + func dismantleUIView(_ webView: WKWebView, coordinator: WebViewCoordinator) { + print("🔴 WebView - DISMANTLING: Starting cleanup") + + // Stop all loading + webView.stopLoading() + + // Remove navigation delegate + webView.navigationDelegate = nil + + // Remove message handlers to prevent memory leaks + webView.configuration.userContentController.removeScriptMessageHandler(forName: "heightUpdate") + webView.configuration.userContentController.removeScriptMessageHandler(forName: "scrollProgress") + + // Clear content + webView.loadHTMLString("", baseURL: nil) + + // Cleanup coordinator + coordinator.cleanup() + + print("🔴 WebView - DISMANTLING: Cleanup completed") + } + func makeCoordinator() -> WebViewCoordinator { WebViewCoordinator() } @@ -321,6 +344,12 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler var pendingHeight: CGFloat = 0 var scrollVelocity: Double = 0 var lastScrollTime: Date = Date() + private var isCleanedUp = false + + deinit { + print("🔴 WebViewCoordinator - deinit called") + cleanup() + } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { @@ -407,4 +436,23 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler lastHeight = height onHeightChange?(height) } + + func cleanup() { + guard !isCleanedUp else { return } + isCleanedUp = true + + print("🔴 WebViewCoordinator - cleanup: Invalidating timers") + + // Invalidate all timers + scrollEndTimer?.invalidate() + scrollEndTimer = nil + heightUpdateTimer?.invalidate() + heightUpdateTimer = nil + + // Clear callbacks to prevent memory leaks + onHeightChange = nil + onScroll = nil + + print("🔴 WebViewCoordinator - cleanup: Completed") + } }