refactor: dispatch address lookup handlers via ensureMainThread#1163
refactor: dispatch address lookup handlers via ensureMainThread#1163descorp wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
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.
|
/gemini review |
There was a problem hiding this comment.
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.
…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.
e640b7b to
74dbb99
Compare
|
| import Foundation | ||
|
|
||
| internal func ensureMainThread(_ work: @escaping () -> Void) { | ||
| internal func ensureMainThread(_ work: @escaping @MainActor () -> Void) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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)
}

Summary
Consistency cleanup following the threading validation done for PR #1161.
BaseAddressModule.update(_:)/confirm(_:address:)now useensureMainThread(_:)(matching the pattern used elsewhere, e.g.EmbeddedComponentBusModule) instead of a rawDispatchQueue.main.asyncthat only wrapped the handler invocation while readinglookupHandler/lookupCompletionHandleron the caller's thread. Now the whole body (state read + handler invocation) runs on main.BaseModule.present(component:)now routes throughensureMainThreadas well. This required updatingensureMainThreadto accept a@MainActor-isolated closure (@escaping @MainActor () -> Void) so it can call@MainActor-isolated APIs likeBaseModule.topPresenterProvider, usingMainActor.assumeIsolatedfor the already-on-main branch. This is safe now that the pod compiles at iOS 15.1 (min_ios_version_supportedvia RN 0.85), well aboveassumeIsolated's iOS 13 floor.presenthas 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-mainPresentationDelegatepath (3DS challenge/redirect/voucher/await, already invoked on main by the SDK) changes from always-async (next runloop tick) to synchronous-inline, consistent with howensureMainThreadbehaves everywhere else in the codebase.Tests
Ran the full
AdyenExampleTestssuite viaxcodebuildon iPhone 17 Pro simulator — all 190 tests pass, includingThreadingSafetyTestsandBaseAddressModuleTests.