From 2c3a5d3aecccf4f0416499ba67f75843d09f9d4a Mon Sep 17 00:00:00 2001 From: Adam Ernst Date: Thu, 11 Jun 2026 21:50:34 -0700 Subject: [PATCH] RCTEventEmitter: minor fix to callableJSModule check Summary: Guard against possibly deallocated RCTCallableJSModules. Changelog: [Internal] Reviewed By: fkgozali Differential Revision: D108361186 --- .../React/Modules/RCTEventEmitter.m | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/react-native/React/Modules/RCTEventEmitter.m b/packages/react-native/React/Modules/RCTEventEmitter.m index 4980704e7203..b8bb618c51ad 100644 --- a/packages/react-native/React/Modules/RCTEventEmitter.m +++ b/packages/react-native/React/Modules/RCTEventEmitter.m @@ -14,6 +14,12 @@ @implementation RCTEventEmitter { NSInteger _listenerCount; BOOL _observationDisabled; + // Set to YES when -setCallableJSModules: is called with a non-nil value. + // _callableJSModules is weak and can return to nil after wiring (e.g. on + // host teardown) while this instance lives on, so the current value of + // _callableJSModules can't tell us whether we were ever set up correctly. + // This flag can. + BOOL _callableJSModulesWasInitialized; } @synthesize callableJSModules = _callableJSModules; @@ -40,15 +46,14 @@ - (instancetype)initWithDisabledObservation - (void)sendEventWithName:(NSString *)eventName body:(id)body { - // Assert that subclasses of RCTEventEmitter does not have `@synthesize _callableJSModules` - // which would cause _callableJSModules in the parent RCTEventEmitter to be nil. RCTAssert( - _callableJSModules != nil, - @"Error when sending event: %@ with body: %@. " + _callableJSModulesWasInitialized, + @"Error when sending event: %@ (listenerCount: %lld) with body: %@. " "RCTCallableJSModules is not set. This is probably because you've " "explicitly synthesized the RCTCallableJSModules in %@, even though it's inherited " "from RCTEventEmitter.", eventName, + (long long)_listenerCount, body, [self class]); @@ -62,15 +67,25 @@ - (void)sendEventWithName:(NSString *)eventName body:(id)body BOOL shouldEmitEvent = (_observationDisabled || _listenerCount > 0); - if (shouldEmitEvent && _callableJSModules) { - [_callableJSModules invokeModule:@"RCTDeviceEventEmitter" - method:@"emit" - withArgs:body ? @[ eventName, body ] : @[ eventName ]]; + // _callableJSModules is weak, so read it exactly once into a strong local. + RCTCallableJSModules *callableJSModules = _callableJSModules; + if (shouldEmitEvent && callableJSModules) { + [callableJSModules invokeModule:@"RCTDeviceEventEmitter" + method:@"emit" + withArgs:body ? @[ eventName, body ] : @[ eventName ]]; } else { RCTLogWarn(@"Sending `%@` with no listeners registered.", eventName); } } +- (void)setCallableJSModules:(RCTCallableJSModules *)callableJSModules +{ + _callableJSModules = callableJSModules; + if (callableJSModules != nil) { + _callableJSModulesWasInitialized = YES; + } +} + - (void)startObserving { // Does nothing