0

I'm trying to create a wrapper around a WKWebView for SwiftUI, and I'm not sure how to prevent a memory leak.

I've created an ObservableObject which handles controlling the WebView, and a custom view that displays it.

The web view needs to be able to communicate with JavaScript, so I've added an onAction() method which gets called when the WebView gets a javascript event.

public class WebViewStore: ObservableObject {
    var webView: WKWebView = WKWebView()

    // List of event handlers that will be called from the WebView
    var eventHandlers: [String: () -> Void] = [:]
    
    deinit {
        // This is never called once an action has been set with the view, 
        // and the content view is destroyed
        print("deinit")
    }
    
    // All the WebView code, including custom JavaScript message handlers, custom scheme handlers, etc...
    func reloadWebView() { }
}

View wrapper:

struct WebView: NSViewRepresentable {
    let store: WebViewStore
    
    func makeNSView(context: Context) -> WKWebView {
        return store.webView
    }
    
    func updateNSView(_ view: WKWebView, context: Context) {}
}

extension WebView {
    /// Action event called from the WebView
    public func onAction(name: String, action: @escaping () -> Void) -> WebView {
        self.store.eventHandlers[name] = action
        return self
    }
}

Usage that creates the retain cycle:

struct ContentView: View {
    
    @StateObject var store = WebViewStore()
    
    var body: some View {
        VStack {
            // Example of interacting with the WebView
            Button("Reload") {
                store.reloadWebView()
            }
            
            WebView(store: store)
                // This action closure captures the WebViewStore, causing the retain cycle.
                .onAction(name: "javascriptMessage") {
                    print("Event!")
                }
        }
    }
}

Is there a way to prevent the retain cycle from happening, or is there a different SwiftUI pattern that can to handle this use case? I can't use [weak self] because the view is a struct. I need to be able to receive events from the WebView and send them to SwiftUI and vice versa.

fuzzyCap
  • 391
  • 4
  • 14
  • 1
    It is just wrong approach, Apple designed explicit pattern to avoid cycling - Representable/Coordinator (which have aligned life-cycles). See here for example of usage with WKWebView https://stackoverflow.com/a/60173992/12299030 or https://stackoverflow.com/a/63433297/12299030 (or find others just by keywords). – Asperi Aug 05 '22 at 09:03
  • Thanks, that does seem to be the better approach. Do you know how it would be possible to call methods on the WebView from the parent view? For example, how would I reload the WebView from a button press like in my example? – fuzzyCap Aug 05 '22 at 09:14
  • Via binding (it calls, for macOS, updateNSView), example is here https://stackoverflow.com/a/61059318/12299030. – Asperi Aug 05 '22 at 09:31

0 Answers0