Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POS: Fix retain cycles related to payment onboarding view #15083

Merged
merged 7 commits into from
Feb 7, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ final class CardPresentPaymentsOnboardingPresenterAdaptor: CardPresentPaymentsOn

private let readinessUseCase: CardPresentPaymentsReadinessUseCase

private let onboardingViewModel: CardPresentPaymentsOnboardingViewModel
private var onboardingViewModel: CardPresentPaymentsOnboardingViewModel {
CardPresentPaymentsOnboardingViewModel(useCase: onboardingUseCase)
}

private var readinessSubscription: AnyCancellable?

Expand All @@ -20,7 +22,6 @@ final class CardPresentPaymentsOnboardingPresenterAdaptor: CardPresentPaymentsOn
init(stores: StoresManager = ServiceLocator.stores) {
onboardingUseCase = CardPresentPaymentsOnboardingUseCase(stores: stores)
readinessUseCase = CardPresentPaymentsReadinessUseCase(onboardingUseCase: onboardingUseCase, stores: stores)
onboardingViewModel = CardPresentPaymentsOnboardingViewModel(useCase: onboardingUseCase)
onboardingScreenViewModelPublisher = onboardingScreenViewModelSubject.eraseToAnyPublisher()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,15 @@ extension PointOfSaleAggregateModel {

func connectCardReader() {
analytics.track(.pointOfSaleCardReaderConnectionTapped)
Task { @MainActor in
_ = try await cardPresentPaymentService.connectReader(using: .bluetooth)
Task { @MainActor [weak self] in
_ = try await self?.cardPresentPaymentService.connectReader(using: .bluetooth)
Comment on lines +163 to +164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really form part of a retain cycle? We don't retain the Task, so I'm not sure why this is needed...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tasks are not released until the asynchronous work inside it finishes.

As I tested, attemptConnection never finishes when onboarding is opened and closed. Task is not released until the connectReader finishes and it doesn't finish. Since Task retains self through accessing cardPresentPaymentService, self (PointOfSaleAggregateModel) remains retained.

Task.weak.self.720p.mov

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tasks are not released until the asynchronous work inside it finishes.

That's tricky to "visually" catch, as there are no explicit references to self. Thanks for the work on this and the explanation!

}
}

func disconnectCardReader() {
analytics.track(.cardReaderDisconnectTapped)
Task { @MainActor in
await cardPresentPaymentService.disconnectReader()
Task { @MainActor [weak self] in
await self?.cardPresentPaymentService.disconnectReader()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ private extension PointOfSaleDashboardView {
}

func paymentsOnboardingView(from onboardingViewModel: CardPresentPaymentsOnboardingViewModel) -> some View {
onboardingViewModel.showSupport = {
posModel.cancelCardPaymentsOnboarding()
onboardingViewModel.showSupport = { [weak posModel] in
posModel?.cancelCardPaymentsOnboarding()
showSupport = true
}
return PointOfSaleCardPresentPaymentOnboardingView(viewModel: .init(onboardingViewModel: onboardingViewModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ final class CardPresentPaymentsOnboardingViewModel: ObservableObject, PaymentSet
self?.updateLearnMoreURL(state: result)
self?.reevaluateShouldShow(onboardingState: result)
})
.handleEvents(receiveOutput: trackState(_:))
.handleEvents(receiveOutput: { [weak self] state in
self?.trackState(state)
})
Comment on lines +36 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great catch, it's not obvious at all that this captures self. I know it does, but it doesn't stand out.

.assign(to: &$state)
}

Expand Down
Loading