Skip to content

refactor: dispatch address lookup handlers via ensureMainThread#1163

Open
descorp wants to merge 8 commits into
developfrom
refactor/threading-consistency
Open

refactor: dispatch address lookup handlers via ensureMainThread#1163
descorp wants to merge 8 commits into
developfrom
refactor/threading-consistency

Conversation

@descorp

@descorp descorp commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Consistency cleanup following the threading validation done for PR #1161.

  1. BaseAddressModule.update(_:) / confirm(_:address:) now use ensureMainThread(_:) (matching the pattern used elsewhere, e.g. EmbeddedComponentBusModule) instead of a raw DispatchQueue.main.async that only wrapped the handler invocation while reading lookupHandler / lookupCompletionHandler on the caller's thread. Now the whole body (state read + handler invocation) runs on main.

  2. BaseModule.present(component:) now routes through ensureMainThread as well. This required updating ensureMainThread to accept a @MainActor-isolated closure (@escaping @MainActor () -> Void) so it can call @MainActor-isolated APIs like BaseModule.topPresenterProvider, using MainActor.assumeIsolated for the already-on-main branch. This is safe now that the pod compiles at iOS 15.1 (min_ios_version_supported via RN 0.85), well above assumeIsolated's iOS 13 floor.

    • Behavioral note: present has two call paths. The off-main RN bridge path (ApplePay/DropIn/Instant modules, called from React Native's background queue) is unchanged — still dispatched async to main. The on-main PresentationDelegate path (3DS challenge/redirect/voucher/await, already invoked on main by the SDK) changes from always-async (next runloop tick) to synchronous-inline, consistent with how ensureMainThread behaves everywhere else in the codebase.

Tests

Ran the full AdyenExampleTests suite via xcodebuild on iPhone 17 Pro simulator — all 190 tests pass, including ThreadingSafetyTests and BaseAddressModuleTests.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the update and confirm methods in BaseAddressModule.swift to execute their operations within ensureMainThread blocks using weak self-references. Feedback on this change highlights a performance regression in the update method, where CPU-intensive decoding and mapping of the results array is now unnecessarily performed on the main thread. It is recommended to keep the decoding on the caller's thread and only dispatch the final handler invocation to the main thread.

Comment thread ios/Components/Base/BaseAddressModule.swift Outdated
@descorp

descorp commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors threading logic across multiple iOS modules by replacing direct DispatchQueue.main.async calls with a centralized ensureMainThread helper, which has been updated to support @mainactor isolation. While these changes improve thread safety, the review highlights several remaining data races where mutable properties or handlers are accessed on background threads before dispatching to the main thread (specifically in CardComponentViewProxy, ApplePayModule, and DropInModule). Additionally, the feedback suggests resolving potential strict concurrency compiler warnings in ThreadUtilities and optimizing error handling in BaseAddressModule and EmbeddedComponentBusModule by returning early on failure to prevent unnecessary decoding.

Comment thread ios/Views/CardView/CardComponentViewProxy.swift
Comment thread ios/Components/ApplePay/ApplePayModule+Delegates.swift Outdated
Comment thread ios/Components/ApplePay/ApplePayModule+Delegates.swift Outdated
Comment thread ios/Components/ApplePay/ApplePayModule+Delegates.swift Outdated
Comment thread ios/Components/DropIn/DropInModule+Delegates.swift Outdated
Comment thread ios/Components/DropIn/DropInModule+Delegates.swift Outdated
Comment thread ios/Components/Base/BaseAddressModule.swift
Comment thread ios/Components/Base/ThreadUtilities.swift Outdated
Comment thread ios/Components/Embedded/EmbeddedComponentBusModule.swift
descorp added 7 commits July 10, 2026 15:15
…veToWindow

Replace the DispatchQueue.main.async deferral in embedComponentView with a
deterministic two-phase approach: loadComponentView (view setup) runs immediately,
and attachChildViewControllerIfNeeded (child VC attachment) runs from both
performInitialization and didMoveToWindow. This handles Fabric mount lifecycle
where props and hierarchy insertion can arrive in either order, eliminating the
timing-dependent parentViewController lookup that could silently skip attachment.
@descorp descorp force-pushed the refactor/threading-consistency branch from e640b7b to 74dbb99 Compare July 10, 2026 14:19
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
64.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

import Foundation

internal func ensureMainThread(_ work: @escaping () -> Void) {
internal func ensureMainThread(_ work: @escaping @MainActor () -> Void) {

@nauaros nauaros Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe this one could be marked as @MainActor but I am not entirely sure if it would work

lookupCompletionHandler(.success(addressModel.postalAddress))
} catch {
lookupCompletionHandler(.failure(error))
do {

@nauaros nauaros Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

non-blocking: We could do

let result: Result<PostalAddress, Error>

do {
    let addressModel: LookupAddressModel = try address.decode()
    result = .success(addressModel.postalAddress)
} catch {
    result = .failure(error)
}

ensureMainThread { [weak self] in
    self?.lookupCompletionHandler?(result)
}

nauaros
nauaros previously approved these changes Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants