From 2b04812cdbbe29a41243f5eaec4188cad3445053 Mon Sep 17 00:00:00 2001 From: tux3 Date: Tue, 30 Apr 2024 15:15:49 +0200 Subject: [PATCH] fix(DTXReactNativeSupport): JS and Content can load in any order Previously waitForReactNativeLoadWithCompletionHandler would add an observer for "RCTJavaScriptDidLoadNotification", then when notified would add a second observer for "RCTContentDidAppearNotification", and after getting a notification for both it would call the completion handler. However, with the React Native new architecture the loading of the JS and Content can happen in a different order (content first). This would cause DetoxSync to get stuck when enabling synchronization. Instead, we register all observers (JS, content, failure) one after the other, and we use a race-free C11 atomic to count when we receive a successful JS or Content notification. After we receive both (or fail), we call the handler as before. --- .../DTXReactNativeSupport.m | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/DetoxSync/DetoxSync/ReactNativeSupport/DTXReactNativeSupport.m b/DetoxSync/DetoxSync/ReactNativeSupport/DTXReactNativeSupport.m index 5e8e872..4c8fcd6 100644 --- a/DetoxSync/DetoxSync/ReactNativeSupport/DTXReactNativeSupport.m +++ b/DetoxSync/DetoxSync/ReactNativeSupport/DTXReactNativeSupport.m @@ -212,23 +212,44 @@ + (void)waitForReactNativeLoadWithCompletionHandler:(void (^)(void))handler { NSParameterAssert(handler != nil); - __block __weak id observer; - __block __weak id observer2; - - observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"RCTJavaScriptDidLoadNotification" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - [[NSNotificationCenter defaultCenter] removeObserver:observer2]; + __block __weak id jsObserver; + __block __weak id contentObserver; + __block __weak id failObserver; + + // JavascriptDidLoad and ContentDidAppear can happen in any order + // When we receive a notification (either of them), we set this to 1 (atomically) + // If it was already at 1, then we received both, and so we can call the handler + static _Thread_local _Atomic int successfulNotificationsReceived; + atomic_store(&successfulNotificationsReceived, 0); + + jsObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"RCTJavaScriptDidLoadNotification" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { + [[NSNotificationCenter defaultCenter] removeObserver:jsObserver]; + + // If the flag was already at 1 then we just received the 2nd, so we call the handler + int expected = 0; + if (!atomic_compare_exchange_strong(&successfulNotificationsReceived, &expected, 1)) + { + [[NSNotificationCenter defaultCenter] removeObserver:failObserver]; + handler(); + } + }]; - observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"RCTContentDidAppearNotification" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; + contentObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"RCTContentDidAppearNotification" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { + [[NSNotificationCenter defaultCenter] removeObserver:contentObserver]; + // If the flag was already at 1 then we just received the 2nd, so we call the handler + int expected = 0; + if (!atomic_compare_exchange_strong(&successfulNotificationsReceived, &expected, 1)) + { + [[NSNotificationCenter defaultCenter] removeObserver:failObserver]; handler(); - }]; + } }]; - observer2 = [[NSNotificationCenter defaultCenter] addObserverForName:@"RCTJavaScriptDidFailToLoadNotification" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - [[NSNotificationCenter defaultCenter] removeObserver:observer2]; + failObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"RCTJavaScriptDidFailToLoadNotification" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { + [[NSNotificationCenter defaultCenter] removeObserver:jsObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:contentObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:failObserver]; handler(); }];