diff --git a/apple/LayoutReanimation/REASwizzledUIManager.mm b/apple/LayoutReanimation/REASwizzledUIManager.mm index fad0ca4c08c0..f931505a8aa7 100644 --- a/apple/LayoutReanimation/REASwizzledUIManager.mm +++ b/apple/LayoutReanimation/REASwizzledUIManager.mm @@ -7,6 +7,8 @@ #import #import #import +#import +#import @interface RCTUIManager (Reanimated) @property REAAnimationsManager *animationsManager; @@ -28,12 +30,22 @@ - (id)animationsManager @implementation REASwizzledUIManager +std::atomic isFlushingBlocks; +std::atomic hasPendingBlocks; + - (instancetype)initWithUIManager:(RCTUIManager *)uiManager withAnimationManager:(REAAnimationsManager *)animationsManager { if (self = [super init]) { + isFlushingBlocks = 0; + hasPendingBlocks = false; [uiManager setAnimationsManager:animationsManager]; [self swizzleMethods]; + + IMP isExecutingUpdatesBatchImpl = imp_implementationWithBlock(^() { + return hasPendingBlocks || isFlushingBlocks > 0; + }); + class_addMethod([RCTUIManager class], @selector(isExecutingUpdatesBatch), isExecutingUpdatesBatchImpl, ""); } return self; } @@ -55,6 +67,18 @@ - (void)swizzleMethods forClass:[RCTUIManager class] with:manageChildrenReanimated fromClass:[self class]]; + [REAUtils swizzleMethod:@selector(addUIBlock:) + forClass:[RCTUIManager class] + with:@selector(reanimated_addUIBlock:) + fromClass:[self class]]; + [REAUtils swizzleMethod:@selector(prependUIBlock:) + forClass:[RCTUIManager class] + with:@selector(reanimated_prependUIBlock:) + fromClass:[self class]]; + [REAUtils swizzleMethod:@selector(flushUIBlocksWithCompletion:) + forClass:[RCTUIManager class] + with:@selector(reanimated_flushUIBlocksWithCompletion:) + fromClass:[self class]]; }); } @@ -324,4 +348,32 @@ - (RCTViewManagerUIBlock)reanimated_uiBlockWithLayoutUpdateForRootView:(RCTRootS }; } +- (void)reanimated_addUIBlock:(RCTViewManagerUIBlock)block +{ + RCTAssertUIManagerQueue(); + hasPendingBlocks = true; + [self reanimated_addUIBlock:block]; +} + +- (void)reanimated_prependUIBlock:(RCTViewManagerUIBlock)block +{ + RCTAssertUIManagerQueue(); + hasPendingBlocks = true; + [self reanimated_prependUIBlock:block]; +} + +- (void)reanimated_flushUIBlocksWithCompletion:(void (^)(void))completion +{ + RCTAssertUIManagerQueue(); + if (hasPendingBlocks) { + ++isFlushingBlocks; + hasPendingBlocks = false; + [self reanimated_addUIBlock:^( + __unused RCTUIManager *manager, __unused NSDictionary *viewRegistry) { + --isFlushingBlocks; + }]; + } + [self reanimated_flushUIBlocksWithCompletion:completion]; +} + @end diff --git a/apple/REANodesManager.mm b/apple/REANodesManager.mm index a3a533adf306..ed36c9986cca 100644 --- a/apple/REANodesManager.mm +++ b/apple/REANodesManager.mm @@ -34,12 +34,12 @@ - (void)updateView:(nonnull NSNumber *)reactTag viewName:(NSString *)viewName pr - (void)setNeedsLayout; +- (bool)isExecutingUpdatesBatch; + @end @interface RCTUIManager (SyncUpdates) -- (BOOL)hasEnqueuedUICommands; - - (void)runSyncUIUpdatesWithObserver:(id)observer; @end @@ -57,12 +57,6 @@ @implementation ComponentUpdate @implementation RCTUIManager (SyncUpdates) -- (BOOL)hasEnqueuedUICommands -{ - // Accessing some private bits of RCTUIManager to provide missing functionality - return [[self valueForKey:@"_pendingUIBlocks"] count] > 0; -} - - (void)runSyncUIUpdatesWithObserver:(id)observer { // before we run uimanager batch complete, we override coordinator observers list @@ -336,7 +330,10 @@ - (void)performOperations if (strongSelf == nil) { return; } - BOOL canUpdateSynchronously = trySynchronously && ![strongSelf.uiManager hasEnqueuedUICommands]; + // It is safe to call `isExecutingUpdatesBatch` in this context (Shadow thread) because both + // the Shadow thread and UI Thread are locked at this point. The UI Thread is specifically + // locked by the lock inside `REASyncUpdateObserver`, ensuring a safe reading operation. + BOOL canUpdateSynchronously = trySynchronously && ![strongSelf.uiManager isExecutingUpdatesBatch]; if (!canUpdateSynchronously) { [syncUpdateObserver unblockUIThread];