如何避免自定义SwiftUI WKWebView封装中的闭包循环引用?
SwiftUI封装WKWebView:避免内存泄漏+双向通信方案
核心问题拆解
循环引用的根源是WKWebView的消息处理器与WebViewStore形成强引用链,加上SwiftUI视图是结构体无法直接在闭包中使用[weak self],导致WebViewStore的deinit无法触发。
具体解决方法
1. 用弱引用代理类处理JS消息
不要让WebViewStore直接实现WKScriptMessageHandler,而是创建独立的代理类持有WebViewStore的弱引用,作为WKWebView的消息中转层:
class ScriptMessageProxy: NSObject, WKScriptMessageHandler { weak var store: WebViewStore? init(store: WebViewStore) { self.store = store super.init() } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { store?.handleJSMessage(message) } }
在WebViewStore中初始化代理并添加到配置:
class WebViewStore: ObservableObject { var webView: WKWebView? private var messageProxy: ScriptMessageProxy? func setupWebView() { let config = WKWebViewConfiguration() messageProxy = ScriptMessageProxy(store: self) config.userContentController.add(messageProxy!, name: "jsBridge") webView = WKWebView(frame: .zero, configuration: config) } }
代理与WebViewStore是弱引用关系,不会形成循环。
2. 用@Published替代闭包传递事件
把原来的onAction闭包换成WebViewStore中的@Published属性,通过Combine订阅实现事件通知:
// WebViewStore中定义事件模型 struct JSAction: Identifiable { let id = UUID() let type: String let data: [String: Any] } class WebViewStore: ObservableObject { @Published var jsAction: JSAction? func handleJSMessage(_ message: WKScriptMessage) { guard let data = message.body as? [String: Any], let type = data["type"] as? String else { return } jsAction = JSAction(type: type, data: data) } }
SwiftUI视图中通过onReceive监听事件:
struct WebViewContainer: View { @StateObject var store = WebViewStore() var body: some View { WebView(store: store) .onReceive(store.$jsAction) { action in guard let action = action else { return } // 根据action.type处理对应逻辑 } } }
这种方式完全避免了闭包带来的强引用问题。
3. 手动清理WebView生命周期
在WebViewStore的deinit中主动销毁WebView并清理配置,确保引用链断开:
class WebViewStore: ObservableObject { // ...其他代码 deinit { webView?.configuration.userContentController.removeAllScriptMessageHandlers() webView = nil print("WebViewStore 已销毁") } }
4. 双向通信实现
- Swift调用JS:使用
evaluateJavaScript时,闭包中用[weak self]避免强引用:func callJS(_ functionName: String, params: [Any]) { guard let jsonData = try? JSONSerialization.data(withJSONObject: params), let paramsStr = String(data: jsonData, encoding: .utf8) else { return } let script = "\(functionName)(\(paramsStr))" webView?.evaluateJavaScript(script) { [weak self] result, error in if let error = error { print("JS调用失败: \(error)") return } // 处理JS返回结果 } } - JS调用Swift:通过WebKit的消息桥发送事件,由代理转发到
WebViewStore:// JS代码 window.webkit.messageHandlers.jsBridge.postMessage({ type: "login", data: {username: "test"} });
内容的提问来源于stack exchange,提问作者Jordan Ellis
相关产品推荐
相关产品推荐

