From 8e6c93dc8dd9da3775a7d7b89331989e1ee06e9a Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Thu, 9 Jan 2025 16:31:34 +0900 Subject: [PATCH 1/5] Prevent memory leaks --- Sources/WebUI/WebView+Extension.swift | 2 +- Sources/WebUI/WebView.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/WebUI/WebView+Extension.swift b/Sources/WebUI/WebView+Extension.swift index 73c2c9a..6fae5dc 100644 --- a/Sources/WebUI/WebView+Extension.swift +++ b/Sources/WebUI/WebView+Extension.swift @@ -20,7 +20,7 @@ extension WebView: View { @MainActor private func makeView() -> Remakeable { let webView = Remakeable { - let wrappedView = EnhancedWKWebView(frame: .zero, configuration: parent.configuration) + let wrappedView = EnhancedWKWebView(frame: .zero, configuration: parent.configuration ?? .init()) parent.applyModifiers(to: wrappedView) return wrappedView } diff --git a/Sources/WebUI/WebView.swift b/Sources/WebUI/WebView.swift index 70a4437..3c5ac86 100644 --- a/Sources/WebUI/WebView.swift +++ b/Sources/WebUI/WebView.swift @@ -13,12 +13,12 @@ import WebKit /// ``` @available(iOS 16.4, macOS 13.3, *) public struct WebView { - let configuration: WKWebViewConfiguration + weak var configuration: WKWebViewConfiguration? private let initialRequest: URLRequest? - private var uiDelegate: (any WKUIDelegate)? - private var navigationDelegate: (any WKNavigationDelegate)? + private weak var uiDelegate: (any WKUIDelegate)? + private weak var navigationDelegate: (any WKNavigationDelegate)? private var isInspectable = false private var allowsBackForwardNavigationGestures = false private var allowsLinkPreview = true @@ -30,7 +30,7 @@ public struct WebView { /// - request: The initial request specifying the URL to load. /// - configuration: The configuration for the new web view. @MainActor - public init(request: URLRequest? = nil, configuration: WKWebViewConfiguration = .init()) { + public init(request: URLRequest? = nil, configuration: WKWebViewConfiguration? = nil) { self.initialRequest = request self.configuration = configuration } From f463a5152c499109eb5d8305941c7c874174dbe8 Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Mon, 13 Jan 2025 13:02:25 +0900 Subject: [PATCH 2/5] Added Unit Tests --- Tests/WebUITests/WeakReference.swift | 8 ++++++++ Tests/WebUITests/WebViewTests.swift | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Tests/WebUITests/WeakReference.swift diff --git a/Tests/WebUITests/WeakReference.swift b/Tests/WebUITests/WeakReference.swift new file mode 100644 index 0000000..455ec9a --- /dev/null +++ b/Tests/WebUITests/WeakReference.swift @@ -0,0 +1,8 @@ +final class WeakReference where T : AnyObject { + weak var value: T? + + func bypass(_ value: T) -> T { + self.value = value + return value + } +} diff --git a/Tests/WebUITests/WebViewTests.swift b/Tests/WebUITests/WebViewTests.swift index 1e37156..7db47f8 100644 --- a/Tests/WebUITests/WebViewTests.swift +++ b/Tests/WebUITests/WebViewTests.swift @@ -2,6 +2,14 @@ import XCTest final class WebViewTests: XCTestCase { + @MainActor + func test_configuration_is_weakly_referenced() { + var configuration = WebViewConfigurationMock?.some(.init()) + let sut = WebView(configuration: configuration) + configuration = nil + XCTAssertNil(sut.configuration) + } + @MainActor func test_applyModifiers_uiDelegate() { let uiDelegateMock = UIDelegateMock() @@ -11,6 +19,16 @@ final class WebViewTests: XCTestCase { XCTAssertTrue(uiDelegateMock === webViewMock.uiDelegate) } + @MainActor + func test_uiDelegate_is_weakly_referenced() { + let actual = WeakReference() + let sut = WebView().uiDelegate(actual.bypass(.init())) + var webViewMock = EnhancedWKWebViewMock?.some(.init()) + sut.applyModifiers(to: webViewMock!) + webViewMock = nil + XCTAssertNil(actual.value) + } + @MainActor func test_applyModifiers_navigationDelegate() { let navigationDelegateMock = NavigationDelegateMock() @@ -20,6 +38,16 @@ final class WebViewTests: XCTestCase { XCTAssertTrue(navigationDelegateMock === webViewMock.navigationDelegateProxy.delegate) } + @MainActor + func test_navigationDelegate_is_weakly_referenced() { + let actual = WeakReference() + let sut = WebView().navigationDelegate(actual.bypass(.init())) + var webViewMock = EnhancedWKWebViewMock?.some(.init()) + sut.applyModifiers(to: webViewMock!) + webViewMock = nil + XCTAssertNil(actual.value) + } + @MainActor func test_applyModifiers_isInspectable() { let sut = WebView().allowsInspectable(true) From 1dcf600f23d68ef580532b4486f23f90defb92f9 Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Wed, 15 Jan 2025 11:24:07 +0900 Subject: [PATCH 3/5] More readable --- Tests/WebUITests/WebViewTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/WebUITests/WebViewTests.swift b/Tests/WebUITests/WebViewTests.swift index 7db47f8..d597751 100644 --- a/Tests/WebUITests/WebViewTests.swift +++ b/Tests/WebUITests/WebViewTests.swift @@ -4,7 +4,7 @@ import XCTest final class WebViewTests: XCTestCase { @MainActor func test_configuration_is_weakly_referenced() { - var configuration = WebViewConfigurationMock?.some(.init()) + var configuration: WebViewConfigurationMock? = .init() let sut = WebView(configuration: configuration) configuration = nil XCTAssertNil(sut.configuration) @@ -23,7 +23,7 @@ final class WebViewTests: XCTestCase { func test_uiDelegate_is_weakly_referenced() { let actual = WeakReference() let sut = WebView().uiDelegate(actual.bypass(.init())) - var webViewMock = EnhancedWKWebViewMock?.some(.init()) + var webViewMock: EnhancedWKWebViewMock? = .init() sut.applyModifiers(to: webViewMock!) webViewMock = nil XCTAssertNil(actual.value) @@ -42,7 +42,7 @@ final class WebViewTests: XCTestCase { func test_navigationDelegate_is_weakly_referenced() { let actual = WeakReference() let sut = WebView().navigationDelegate(actual.bypass(.init())) - var webViewMock = EnhancedWKWebViewMock?.some(.init()) + var webViewMock: EnhancedWKWebViewMock? = .init() sut.applyModifiers(to: webViewMock!) webViewMock = nil XCTAssertNil(actual.value) From d87e08f43ba85366c18fbae4a04b6722e0f3f4ef Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Thu, 16 Jan 2025 15:58:32 +0900 Subject: [PATCH 4/5] Improved WeakReference. --- Tests/WebUITests/WeakReference.swift | 8 -------- Tests/WebUITests/WebViewTests.swift | 16 ++++++++++------ Tests/WebUITests/XCTestCase+Extension.swift | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 14 deletions(-) delete mode 100644 Tests/WebUITests/WeakReference.swift create mode 100644 Tests/WebUITests/XCTestCase+Extension.swift diff --git a/Tests/WebUITests/WeakReference.swift b/Tests/WebUITests/WeakReference.swift deleted file mode 100644 index 455ec9a..0000000 --- a/Tests/WebUITests/WeakReference.swift +++ /dev/null @@ -1,8 +0,0 @@ -final class WeakReference where T : AnyObject { - weak var value: T? - - func bypass(_ value: T) -> T { - self.value = value - return value - } -} diff --git a/Tests/WebUITests/WebViewTests.swift b/Tests/WebUITests/WebViewTests.swift index d597751..5d37c61 100644 --- a/Tests/WebUITests/WebViewTests.swift +++ b/Tests/WebUITests/WebViewTests.swift @@ -21,12 +21,14 @@ final class WebViewTests: XCTestCase { @MainActor func test_uiDelegate_is_weakly_referenced() { - let actual = WeakReference() - let sut = WebView().uiDelegate(actual.bypass(.init())) + var sut = WebView() + let actual = weaklyScope(UIDelegateMock()) { + sut = sut.uiDelegate($0) + } var webViewMock: EnhancedWKWebViewMock? = .init() sut.applyModifiers(to: webViewMock!) webViewMock = nil - XCTAssertNil(actual.value) + XCTAssertNil(actual) } @MainActor @@ -40,12 +42,14 @@ final class WebViewTests: XCTestCase { @MainActor func test_navigationDelegate_is_weakly_referenced() { - let actual = WeakReference() - let sut = WebView().navigationDelegate(actual.bypass(.init())) + var sut = WebView() + let actual = weaklyScope(NavigationDelegateMock()) { + sut = sut.navigationDelegate($0) + } var webViewMock: EnhancedWKWebViewMock? = .init() sut.applyModifiers(to: webViewMock!) webViewMock = nil - XCTAssertNil(actual.value) + XCTAssertNil(actual) } @MainActor diff --git a/Tests/WebUITests/XCTestCase+Extension.swift b/Tests/WebUITests/XCTestCase+Extension.swift new file mode 100644 index 0000000..5e34773 --- /dev/null +++ b/Tests/WebUITests/XCTestCase+Extension.swift @@ -0,0 +1,15 @@ +import XCTest + +extension XCTestCase { + func weaklyScope( + _ instance: @autoclosure () -> T, + perform action: (T) -> () + ) -> T? { + weak var weakValue = { + let value = instance() + action(value) + return value + }() + return weakValue + } +} From 7e24a3c719188974f9dda5a2cf5b179696bedca6 Mon Sep 17 00:00:00 2001 From: "Takuto NAKAMURA (Kyome)" Date: Thu, 16 Jan 2025 16:38:38 +0900 Subject: [PATCH 5/5] Remove unnecessary code. --- Tests/WebUITests/WebViewTests.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/WebUITests/WebViewTests.swift b/Tests/WebUITests/WebViewTests.swift index 5d37c61..4e55e87 100644 --- a/Tests/WebUITests/WebViewTests.swift +++ b/Tests/WebUITests/WebViewTests.swift @@ -25,9 +25,6 @@ final class WebViewTests: XCTestCase { let actual = weaklyScope(UIDelegateMock()) { sut = sut.uiDelegate($0) } - var webViewMock: EnhancedWKWebViewMock? = .init() - sut.applyModifiers(to: webViewMock!) - webViewMock = nil XCTAssertNil(actual) } @@ -46,9 +43,6 @@ final class WebViewTests: XCTestCase { let actual = weaklyScope(NavigationDelegateMock()) { sut = sut.navigationDelegate($0) } - var webViewMock: EnhancedWKWebViewMock? = .init() - sut.applyModifiers(to: webViewMock!) - webViewMock = nil XCTAssertNil(actual) }