Skip to content

Commit 6aee643

Browse files
authored
[PM-20171] Fix ViewItemView retain cycle (#1581)
1 parent 8f04bf8 commit 6aee643

File tree

5 files changed

+40
-2
lines changed

5 files changed

+40
-2
lines changed

BitwardenShared/UI/Vault/VaultItem/ViewItem/ViewItemAction.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ enum ViewItemAction: Equatable, Sendable {
1818
/// The visibility button was pressed for the specified custom field.
1919
case customFieldVisibilityPressed(CustomFieldState)
2020

21+
/// The view item disappeared from the screen.
22+
case disappeared
23+
2124
/// The dismiss button was pressed.
2225
case dismissPressed
2326

@@ -57,7 +60,8 @@ enum ViewItemAction: Equatable, Sendable {
5760
true
5861
case let .copyPressed(_, field):
5962
field.requiresMasterPasswordReprompt
60-
case .dismissPressed,
63+
case .disappeared,
64+
.dismissPressed,
6165
.passwordHistoryPressed,
6266
.toastShown:
6367
false

BitwardenShared/UI/Vault/VaultItem/ViewItem/ViewItemActionTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class ViewItemActionTests: BitwardenTestCase {
7373
.requiresMasterPasswordReprompt
7474
)
7575

76+
XCTAssertFalse(ViewItemAction.disappeared.requiresMasterPasswordReprompt)
7677
XCTAssertFalse(ViewItemAction.dismissPressed.requiresMasterPasswordReprompt)
7778

7879
XCTAssertTrue(ViewItemAction.downloadAttachment(.fixture()).requiresMasterPasswordReprompt)

BitwardenShared/UI/Vault/VaultItem/ViewItem/ViewItemProcessor.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ final class ViewItemProcessor: StateProcessor<ViewItemState, ViewItemAction, Vie
5757
/// The services used by this processor.
5858
private let services: Services
5959

60+
/// The task that streams cipher details.
61+
private(set) var streamCipherDetailsTask: Task<Void, Never>?
62+
6063
// MARK: Initialization
6164

6265
/// Creates a new `ViewItemProcessor`.
@@ -95,7 +98,10 @@ final class ViewItemProcessor: StateProcessor<ViewItemState, ViewItemAction, Vie
9598
override func perform(_ effect: ViewItemEffect) async {
9699
switch effect {
97100
case .appeared:
98-
await streamCipherDetails()
101+
streamCipherDetailsTask?.cancel()
102+
streamCipherDetailsTask = Task {
103+
await streamCipherDetails()
104+
}
99105
case .checkPasswordPressed:
100106
do {
101107
guard let password = state.loadingState.data?.cipher.login?.password else { return }
@@ -155,6 +161,9 @@ final class ViewItemProcessor: StateProcessor<ViewItemState, ViewItemAction, Vie
155161
)
156162
}
157163
}
164+
case .disappeared:
165+
streamCipherDetailsTask?.cancel()
166+
streamCipherDetailsTask = nil
158167
case .dismissPressed:
159168
coordinator.navigate(to: .dismiss())
160169
case let .downloadAttachment(attachment):

BitwardenShared/UI/Vault/VaultItem/ViewItem/ViewItemProcessorTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class ViewItemProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
157157
)
158158
vaultRepository.cipherDetailsSubject.send(cipherItem)
159159

160+
XCTAssertNil(subject.streamCipherDetailsTask)
160161
let task = Task {
161162
await subject.perform(.appeared)
162163
}
@@ -172,6 +173,7 @@ class ViewItemProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
172173

173174
expectedState.allUserCollections = collections
174175

176+
XCTAssertNotNil(subject.streamCipherDetailsTask)
175177
XCTAssertTrue(subject.state.hasPremiumFeatures)
176178
XCTAssertTrue(subject.state.hasMasterPassword)
177179
XCTAssertFalse(subject.state.restrictCipherItemDeletionFlagEnabled)
@@ -781,6 +783,25 @@ class ViewItemProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
781783
XCTAssertEqual(coordinator.routes.last, .dismiss())
782784
}
783785

786+
/// `receive` with `.disappeared` should clear streamCipherDetailsTask.
787+
@MainActor
788+
func test_receive_disappearPressed() {
789+
let account = Account.fixture()
790+
stateService.activeAccount = account
791+
792+
XCTAssertNil(subject.streamCipherDetailsTask)
793+
let task = Task {
794+
await subject.perform(.appeared)
795+
}
796+
797+
waitFor(subject.state.loadingState != .loading(nil))
798+
task.cancel()
799+
800+
XCTAssertNotNil(subject.streamCipherDetailsTask)
801+
subject.receive(.disappeared)
802+
XCTAssertNil(subject.streamCipherDetailsTask)
803+
}
804+
784805
/// `perform(_:)` with `.deletePressed` presents the confirmation alert before delete the item and displays
785806
/// generic error alert if soft deleting fails.
786807
@MainActor

BitwardenShared/UI/Vault/VaultItem/ViewItem/ViewItemView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ struct ViewItemView: View {
107107
await store.perform(.appeared)
108108
}
109109
}
110+
.onDisappear {
111+
store.send(.disappeared)
112+
}
110113
}
111114

112115
// MARK: Private Views

0 commit comments

Comments
 (0)