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:
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() { }
}
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.
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 vice versa.