You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 09:45:25