From 11fdea01815faecc9daa398e0eeb332798776f3f Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 10 Sep 2026 07:04:26 -0700 Subject: [PATCH] Remove legacy RCTTimingModule (#57037) Summary: ## Changelog: [IOS][Fixed] Remove legacy RCTTimingModule Reviewed By: javache Differential Revision: D107201906 --- .../React/Base/RCTBridge+Private.h | 6 - .../react-native/React/Base/RCTDisplayLink.h | 15 +-- .../react-native/React/Base/RCTDisplayLink.m | 124 +++++------------- .../React/CoreModules/RCTTiming.h | 3 +- .../React/CoreModules/RCTTiming.mm | 80 ++--------- .../platform/ios/ReactCommon/RCTInstance.mm | 40 +----- .../api-snapshots/ReactAppleDebugCxx.api | 11 +- .../api-snapshots/ReactAppleNewarchCxx.api | 11 +- .../api-snapshots/ReactAppleReleaseCxx.api | 11 +- 9 files changed, 53 insertions(+), 248 deletions(-) diff --git a/packages/react-native/React/Base/RCTBridge+Private.h b/packages/react-native/React/Base/RCTBridge+Private.h index 85e8a2762b9c..88d68cd925b6 100644 --- a/packages/react-native/React/Base/RCTBridge+Private.h +++ b/packages/react-native/React/Base/RCTBridge+Private.h @@ -86,12 +86,6 @@ RCT_EXTERN void RCTRegisterModule(Class); */ - (void)start; -/** - * Used by RCTModuleData to register the module for frame updates after it is - * lazily initialized. - */ -- (void)registerModuleForFrameUpdates:(id)module withModuleData:(RCTModuleData *)moduleData; - /** * Dispatch work to a module's queue - this is also supports the fake RCTJSThread * queue. Exposed for the RCTProfiler diff --git a/packages/react-native/React/Base/RCTDisplayLink.h b/packages/react-native/React/Base/RCTDisplayLink.h index a8e0c10cfebb..042750a9f3eb 100644 --- a/packages/react-native/React/Base/RCTDisplayLink.h +++ b/packages/react-native/React/Base/RCTDisplayLink.h @@ -7,23 +7,12 @@ #import -@protocol RCTBridgeModule; -@class RCTModuleData; - -@protocol RCTDisplayLinkModuleHolder -- (id)instance; -- (Class)moduleClass; -- (dispatch_queue_t)methodQueue; -@end +@protocol RCTFrameUpdateObserver; @interface RCTDisplayLink : NSObject -- (instancetype)init; +- (instancetype)initWithFrameUpdateObserver:(id)observer; - (void)invalidate; -- (void)registerModuleForFrameUpdates:(id)module - withModuleHolder:(id)moduleHolder - __attribute__((deprecated( - "registerModuleForFrameUpdates is part of the legacy architecture and will be removed in a future React Native release."))); - (void)addToRunLoop:(NSRunLoop *)runLoop; @end diff --git a/packages/react-native/React/Base/RCTDisplayLink.m b/packages/react-native/React/Base/RCTDisplayLink.m index de7c8700f3e5..ebb430f42d93 100644 --- a/packages/react-native/React/Base/RCTDisplayLink.m +++ b/packages/react-native/React/Base/RCTDisplayLink.m @@ -11,9 +11,7 @@ #import #import "RCTAssert.h" -#import "RCTBridgeModule.h" #import "RCTFrameUpdate.h" -#import "RCTModuleData.h" #import "RCTProfile.h" #define RCTAssertRunLoop() \ @@ -21,66 +19,42 @@ @implementation RCTDisplayLink { CADisplayLink *_jsDisplayLink; - NSMutableSet> *_frameUpdateObservers; + id _frameUpdateObserver; NSRunLoop *_runLoop; } -- (instancetype)init +- (instancetype)initWithFrameUpdateObserver:(id)observer { if ((self = [super init])) { - _frameUpdateObservers = [NSMutableSet new]; + _frameUpdateObserver = observer; _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)]; - } - return self; -} + __weak typeof(self) weakSelf = self; + observer.pauseCallback = ^{ + typeof(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } -- (void)registerModuleForFrameUpdates:(id)module - withModuleHolder:(id)moduleHolder -{ - if (![moduleHolder.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)] || - [_frameUpdateObservers containsObject:moduleHolder]) { - return; - } + CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop]; + if (!cfRunLoop) { + return; + } - [_frameUpdateObservers addObject:moduleHolder]; - - // Don't access the module instance via moduleHolder, as this will cause deadlock - id observer = (id)module; - __weak typeof(self) weakSelf = self; - observer.pauseCallback = ^{ - typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop]; - if (!cfRunLoop) { - return; - } - - if ([NSRunLoop currentRunLoop] == strongSelf->_runLoop) { - [weakSelf updateJSDisplayLinkState]; - } else { - CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{ - @autoreleasepool { - [weakSelf updateJSDisplayLinkState]; - } - }); - CFRunLoopWakeUp(cfRunLoop); - } - }; - - // Assuming we're paused right now, we only need to update the display link's state - // when the new observer is not paused. If it not paused, the observer will immediately - // start receiving updates anyway. - if (![observer isPaused] && _runLoop) { - CFRunLoopPerformBlock([_runLoop getCFRunLoop], kCFRunLoopDefaultMode, ^{ - @autoreleasepool { - [self updateJSDisplayLinkState]; + if ([NSRunLoop currentRunLoop] == strongSelf->_runLoop) { + [weakSelf updateJSDisplayLinkState]; + } else { + CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{ + @autoreleasepool { + [weakSelf updateJSDisplayLinkState]; + } + }); + CFRunLoopWakeUp(cfRunLoop); } - }); + }; } + + return self; } - (void)addToRunLoop:(NSRunLoop *)runLoop @@ -96,47 +70,24 @@ - (void)dealloc - (void)invalidate { - // ensure observer callbacks do not hold a reference to weak self via pauseCallback - for (id moduleHolder in _frameUpdateObservers) { - id observer = (id)moduleHolder.instance; - [observer setPauseCallback:nil]; - } - [_frameUpdateObservers removeAllObjects]; // just to be explicit + // ensure the observer callback does not hold a reference to weak self via pauseCallback + [_frameUpdateObserver setPauseCallback:nil]; + _frameUpdateObserver = nil; [_jsDisplayLink invalidate]; } -- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue -{ - if (queue == RCTJSThread) { - block(); - } else if (queue) { - dispatch_async(queue, block); - } -} - - (void)_jsThreadUpdate:(CADisplayLink *)displayLink { RCTAssertRunLoop(); RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTDisplayLink _jsThreadUpdate:]", nil); + // This always runs on the JS thread run loop, which is the queue the frame + // update observer expects its callbacks on, so dispatch inline. RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; - for (id moduleHolder in _frameUpdateObservers) { - id observer = (id)moduleHolder.instance; - if (!observer.paused) { - if (moduleHolder.methodQueue) { - RCTProfileBeginFlowEvent(); - [self - dispatchBlock:^{ - RCTProfileEndFlowEvent(); - [observer didUpdateFrame:frameUpdate]; - } - queue:moduleHolder.methodQueue]; - } else { - [observer didUpdateFrame:frameUpdate]; - } - } + if (!_frameUpdateObserver.paused) { + [_frameUpdateObserver didUpdateFrame:frameUpdate]; } [self updateJSDisplayLinkState]; @@ -150,16 +101,7 @@ - (void)updateJSDisplayLinkState { RCTAssertRunLoop(); - BOOL pauseDisplayLink = YES; - for (id moduleHolder in _frameUpdateObservers) { - id observer = (id)moduleHolder.instance; - if (!observer.paused) { - pauseDisplayLink = NO; - break; - } - } - - _jsDisplayLink.paused = pauseDisplayLink; + _jsDisplayLink.paused = _frameUpdateObserver == nil || _frameUpdateObserver.paused; } @end diff --git a/packages/react-native/React/CoreModules/RCTTiming.h b/packages/react-native/React/CoreModules/RCTTiming.h index 2a7519afb1b6..6b2fe638f3af 100644 --- a/packages/react-native/React/CoreModules/RCTTiming.h +++ b/packages/react-native/React/CoreModules/RCTTiming.h @@ -7,7 +7,6 @@ #import -#import #import #import #import @@ -22,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface RCTTiming : NSObject +@interface RCTTiming : NSObject - (instancetype)initWithDelegate:(id)delegate; - (void)createTimerForNextFrame:(NSNumber *)callbackID diff --git a/packages/react-native/React/CoreModules/RCTTiming.mm b/packages/react-native/React/CoreModules/RCTTiming.mm index ef9e2d3fa00f..d77719a4b5d6 100644 --- a/packages/react-native/React/CoreModules/RCTTiming.mm +++ b/packages/react-native/React/CoreModules/RCTTiming.mm @@ -7,17 +7,11 @@ #import "RCTTiming.h" -#import - #import -#import -#import #import #import #import -#import "CoreModulesPlugins.h" - static const NSTimeInterval kMinimumSleepInterval = 1; // These timing contants should be kept in sync with the ones in `JSTimers.js`. @@ -82,7 +76,7 @@ @implementation _RCTTimingProxy { + (instancetype)proxyWithTarget:(id)target { _RCTTimingProxy *proxy = [self new]; - if (proxy) { + if (proxy != nil) { proxy->_target = target; } return proxy; @@ -103,12 +97,9 @@ @implementation RCTTiming { id _timingDelegate; } -@synthesize bridge = _bridge; @synthesize paused = _paused; @synthesize pauseCallback = _pauseCallback; -RCT_EXPORT_MODULE() - - (instancetype)initWithDelegate:(id)delegate { if (self = [super init]) { @@ -165,15 +156,9 @@ - (void)dealloc [_sleepTimer invalidate]; } -- (dispatch_queue_t)methodQueue -{ - return RCTJSThread; -} - - (void)invalidate { [self stopTimers]; - _bridge = nil; _timingDelegate = nil; } @@ -220,7 +205,7 @@ - (void)stopTimers - (void)startTimers { - if ((!_bridge && !_timingDelegate) || _inBackground || ![self hasPendingTimers]) { + if ((!_timingDelegate) || _inBackground || ![self hasPendingTimers]) { return; } @@ -259,11 +244,7 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update NSArray *sortedTimers = [[timersToCall sortedArrayUsingComparator:^(_RCTTimer *a, _RCTTimer *b) { return [a.target compare:b.target]; }] valueForKey:@"callbackID"]; - if (_bridge) { - [_bridge enqueueJSCall:@"JSTimers" method:@"callTimers" args:@[ sortedTimers ] completion:NULL]; - } else { - [_timingDelegate callTimers:sortedTimers]; - } + [_timingDelegate callTimers:sortedTimers]; } for (_RCTTimer *timer in timersToCall) { @@ -282,23 +263,19 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update if (kFrameDuration - frameElapsed >= kIdleCallbackFrameDeadline) { NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970]; NSNumber *absoluteFrameStartMS = @((currentTimestamp - frameElapsed) * 1000); - if (_bridge) { - [_bridge enqueueJSCall:@"JSTimers" method:@"callIdleCallbacks" args:@[ absoluteFrameStartMS ] completion:NULL]; - } else { - [_timingDelegate callIdleCallbacks:absoluteFrameStartMS]; - } + [_timingDelegate callIdleCallbacks:absoluteFrameStartMS]; } } // Switch to a paused state only if we didn't call any timer this frame, so if // in response to this timer another timer is scheduled, we don't pause and unpause // the displaylink frivolously. - NSUInteger timerCount; + NSUInteger timerCount = 0; @synchronized(_timers) { timerCount = _timers.count; } if (_inBackground) { - if (timerCount) { + if (timerCount != 0u) { [self scheduleSleepTimer:nextScheduledTarget]; } } else if (!_sendIdleEvents && timersToCall.count == 0) { @@ -318,7 +295,7 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update - (void)scheduleSleepTimer:(NSDate *)sleepTarget { @synchronized(self) { - if (!_sleepTimer || !_sleepTimer.valid) { + if ((_sleepTimer == nil) || !_sleepTimer.valid) { _sleepTimer = [[NSTimer alloc] initWithFireDate:sleepTarget interval:0 target:[_RCTTimingProxy proxyWithTarget:self] @@ -343,35 +320,6 @@ - (void)timerDidFire } } -/** - * A method used for asynchronously creating a timer. If the timer has already expired, - * (based on the provided jsSchedulingTime) then it will be immediately invoked. - * - * There's a small difference between the time when we call - * setTimeout/setInterval/requestAnimation frame and the time it actually makes - * it here. This is important and needs to be taken into account when - * calculating the timer's target time. We calculate this by passing in - * Date.now() from JS and then subtracting that from the current time here. - */ -RCT_EXPORT_METHOD( - createTimer : (double)callbackID duration : (NSTimeInterval)jsDuration jsSchedulingTime : (double) - jsSchedulingTime repeats : (BOOL)repeats) -{ - NSNumber *callbackIdObjc = [NSNumber numberWithDouble:callbackID]; - NSDate *schedulingTime = [RCTConvert NSDate:[NSNumber numberWithDouble:jsSchedulingTime]]; - if (jsDuration == 0 && repeats == NO) { - // For super fast, one-off timers, just enqueue them immediately rather than waiting a frame. - if (_bridge) { - [_bridge _immediatelyCallTimer:callbackIdObjc]; - } else { - [_timingDelegate immediatelyCallTimer:callbackIdObjc]; - } - return; - } - - [self createTimerForNextFrame:callbackIdObjc duration:jsDuration jsSchedulingTime:schedulingTime repeats:repeats]; -} - /** * A method used for synchronously creating a timer. The timer will not be invoked until the * next frame, regardless of whether it has already expired (i.e. jsSchedulingTime is 0). @@ -407,24 +355,14 @@ - (void)createTimerForNextFrame:(nonnull NSNumber *)callbackID } } -RCT_EXPORT_METHOD(deleteTimer : (double)timerID) +- (void)deleteTimer:(double)timerID { @synchronized(_timers) { - [_timers removeObjectForKey:[NSNumber numberWithDouble:timerID]]; + [_timers removeObjectForKey:@(timerID)]; } if (![self hasPendingTimers]) { [self stopTimers]; } } -RCT_EXPORT_METHOD(setSendIdleEvents : (BOOL)sendIdleEvents) -{ - _sendIdleEvents = sendIdleEvents; - if (sendIdleEvents) { - [self startTimers]; - } else if (![self hasPendingTimers]) { - [self stopTimers]; - } -} - @end diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index 7197e20847ad..01e337931de1 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -61,37 +61,6 @@ using namespace facebook; using namespace facebook::react; -__attribute__((deprecated( - "RCTBridgelessDisplayLinkModuleHolder is part of the legacy architecture and will be removed in a future React Native release."))) -@interface RCTBridgelessDisplayLinkModuleHolder : NSObject -- (instancetype)initWithModule:(id)module; -@end - -@implementation RCTBridgelessDisplayLinkModuleHolder { - id _module; -} -- (instancetype)initWithModule:(id)module -{ - _module = module; - return self; -} - -- (id)instance -{ - return _module; -} - -- (Class)moduleClass -{ - return [_module class]; -} - -- (dispatch_queue_t)methodQueue -{ - return _module.methodQueue; -} -@end - @interface RCTInstance () @end @@ -423,7 +392,7 @@ - (void)_start }]; // DisplayLink is used to call timer callbacks. - _displayLink = [RCTDisplayLink new]; + _displayLink = [[RCTDisplayLink alloc] initWithFrameUpdateObserver:timing]; auto &inspectorFlags = jsinspector_modern::InspectorFlags::getInstance(); ReactInstance::JSRuntimeFlags options = {.isProfiling = inspectorFlags.getIsProfilingBuild()}; @@ -447,12 +416,7 @@ - (void)_start [strongSelf->_delegate instance:strongSelf didInitializeRuntime:runtime]; -// Set up Display Link -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - id moduleHolder = [[RCTBridgelessDisplayLinkModuleHolder alloc] initWithModule:timing]; - [strongSelf->_displayLink registerModuleForFrameUpdates:timing withModuleHolder:moduleHolder]; -#pragma clang diagnostic pop + // Set up Display Link [strongSelf->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]]; // Attempt to load bundle synchronously, fallback to asynchronously. diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 29a12a04d1d7..417012bf9b05 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -1028,10 +1028,9 @@ interface RCTDiffClampAnimatedNode : public RCTValueAnimatedNode { } interface RCTDisplayLink : public NSObject { - public virtual instancetype init(); + public virtual instancetype initWithFrameUpdateObserver:(id observer); public virtual void addToRunLoop:(NSRunLoop* runLoop); public virtual void invalidate(); - public virtual void registerModuleForFrameUpdates:withModuleHolder:(id module, id moduleHolder); } interface RCTDisplayWeakRefreshable : public NSObject { @@ -1982,7 +1981,7 @@ interface RCTThirdPartyComponentsProvider : public NSObject { public virtual static NSDictionary>* thirdPartyFabricComponents(); } -interface RCTTiming : public NSObject { +interface RCTTiming : public NSObject { public virtual instancetype initWithDelegate:(id delegate); public virtual void createTimerForNextFrame:duration:jsSchedulingTime:repeats:(NSNumber* callbackID, NSTimeInterval jsDuration, _Nullable NSDate* jsSchedulingTime, BOOL repeats); public virtual void deleteTimer:(double timerID); @@ -2773,12 +2772,6 @@ protocol RCTDevSettingsInspectable : public NSObject { public @property (assign) BOOL isInspectable; } -protocol RCTDisplayLinkModuleHolder { - public virtual Class moduleClass(); - public virtual dispatch_queue_t methodQueue(); - public virtual id instance(); -} - protocol RCTDisplayRefreshable { public virtual void displayDidRefresh:(CADisplayLink* displayLink); } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index c7108e0046a2..6f2582fe3ef8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -1028,10 +1028,9 @@ interface RCTDiffClampAnimatedNode : public RCTValueAnimatedNode { } interface RCTDisplayLink : public NSObject { - public virtual instancetype init(); + public virtual instancetype initWithFrameUpdateObserver:(id observer); public virtual void addToRunLoop:(NSRunLoop* runLoop); public virtual void invalidate(); - public virtual void registerModuleForFrameUpdates:withModuleHolder:(id module, id moduleHolder); } interface RCTDisplayWeakRefreshable : public NSObject { @@ -1975,7 +1974,7 @@ interface RCTThirdPartyComponentsProvider : public NSObject { public virtual static NSDictionary>* thirdPartyFabricComponents(); } -interface RCTTiming : public NSObject { +interface RCTTiming : public NSObject { public virtual instancetype initWithDelegate:(id delegate); public virtual void createTimerForNextFrame:duration:jsSchedulingTime:repeats:(NSNumber* callbackID, NSTimeInterval jsDuration, _Nullable NSDate* jsSchedulingTime, BOOL repeats); public virtual void deleteTimer:(double timerID); @@ -2765,12 +2764,6 @@ protocol RCTDevSettingsInspectable : public NSObject { public @property (assign) BOOL isInspectable; } -protocol RCTDisplayLinkModuleHolder { - public virtual Class moduleClass(); - public virtual dispatch_queue_t methodQueue(); - public virtual id instance(); -} - protocol RCTDisplayRefreshable { public virtual void displayDidRefresh:(CADisplayLink* displayLink); } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 799d86650b9e..cceae2b9f4bf 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -1028,10 +1028,9 @@ interface RCTDiffClampAnimatedNode : public RCTValueAnimatedNode { } interface RCTDisplayLink : public NSObject { - public virtual instancetype init(); + public virtual instancetype initWithFrameUpdateObserver:(id observer); public virtual void addToRunLoop:(NSRunLoop* runLoop); public virtual void invalidate(); - public virtual void registerModuleForFrameUpdates:withModuleHolder:(id module, id moduleHolder); } interface RCTDisplayWeakRefreshable : public NSObject { @@ -1982,7 +1981,7 @@ interface RCTThirdPartyComponentsProvider : public NSObject { public virtual static NSDictionary>* thirdPartyFabricComponents(); } -interface RCTTiming : public NSObject { +interface RCTTiming : public NSObject { public virtual instancetype initWithDelegate:(id delegate); public virtual void createTimerForNextFrame:duration:jsSchedulingTime:repeats:(NSNumber* callbackID, NSTimeInterval jsDuration, _Nullable NSDate* jsSchedulingTime, BOOL repeats); public virtual void deleteTimer:(double timerID); @@ -2773,12 +2772,6 @@ protocol RCTDevSettingsInspectable : public NSObject { public @property (assign) BOOL isInspectable; } -protocol RCTDisplayLinkModuleHolder { - public virtual Class moduleClass(); - public virtual dispatch_queue_t methodQueue(); - public virtual id instance(); -} - protocol RCTDisplayRefreshable { public virtual void displayDidRefresh:(CADisplayLink* displayLink); }