@@ -125,13 +125,12 @@ void onBatchComplete() override {
125125 [bridge_ partialBatchDidFlush ];
126126 [bridge_ batchDidComplete ];
127127 }
128- void incrementPendingJSCalls () override {}
129- void decrementPendingJSCalls () override {}
130128};
131129
132130@implementation RCTCxxBridge
133131{
134132 BOOL _wasBatchActive;
133+ BOOL _didInvalidate;
135134
136135 NSMutableArray <dispatch_block_t > *_pendingCalls;
137136 std::atomic<NSInteger > _pendingCount;
@@ -169,7 +168,7 @@ - (JSContext *)jsContext
169168
170169- (JSGlobalContextRef)jsContextRef
171170{
172- return (JSGlobalContextRef)self->_reactInstance -> getJavaScriptContext ();
171+ return (JSGlobalContextRef)( self->_reactInstance ? self-> _reactInstance -> getJavaScriptContext () : nullptr );
173172}
174173
175174- (instancetype )initWithParentBridge : (RCTBridge *)bridge
@@ -200,7 +199,7 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge
200199 return self;
201200}
202201
203- - (void )runJSRunLoop
202+ + (void )runRunLoop
204203{
205204 @autoreleasepool {
206205 RCT_PROFILE_BEGIN_EVENT (RCTProfileTagAlways, @" -[RCTCxxBridge runJSRunLoop] setup" , nil );
@@ -263,8 +262,8 @@ - (void)start
263262 object: _parentBridge userInfo: @{@" bridge" : self}];
264263
265264 // Set up the JS thread early
266- _jsThread = [[NSThread alloc ] initWithTarget: self
267- selector: @selector (runJSRunLoop )
265+ _jsThread = [[NSThread alloc ] initWithTarget: [ self class ]
266+ selector: @selector (runRunLoop )
268267 object: nil ];
269268 _jsThread.name = RCTJSThreadName;
270269 _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
@@ -469,7 +468,7 @@ - (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
469468 if (_reactInstance) {
470469 // This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
471470 _reactInstance->initializeBridge (
472- std::unique_ptr <RCTInstanceCallback>(new RCTInstanceCallback ( self) ),
471+ std::make_unique <RCTInstanceCallback>(self),
473472 executorFactory,
474473 _jsMessageThread,
475474 [self _buildModuleRegistry ]);
@@ -769,6 +768,7 @@ - (void)handleError:(NSError *)error
769768 }
770769
771770 RCTFatal (error);
771+
772772 // RN will stop, but let the rest of the app keep going.
773773 return ;
774774 }
@@ -779,27 +779,27 @@ - (void)handleError:(NSError *)error
779779
780780 // Hack: once the bridge is invalidated below, it won't initialize any new native
781781 // modules. Initialize the redbox module now so we can still report this error.
782- [self redBox ];
782+ RCTRedBox *redBox = [self redBox ];
783783
784784 _loading = NO ;
785785 _valid = NO ;
786786
787787 dispatch_async (dispatch_get_main_queue (), ^{
788788 if (self->_jsMessageThread ) {
789- auto thread = self->_jsMessageThread ;
790- self->_jsMessageThread ->runOnQueue ([thread] {
791- thread->quitSynchronous ();
792- });
793- self->_jsMessageThread .reset ();
789+ // Make sure initializeBridge completed
790+ self->_jsMessageThread ->runOnQueueSync ([] {});
794791 }
795792
793+ self->_reactInstance .reset ();
794+ self->_jsMessageThread .reset ();
795+
796796 [[NSNotificationCenter defaultCenter ]
797797 postNotificationName: RCTJavaScriptDidFailToLoadNotification
798798 object: self ->_parentBridge userInfo: @{@" bridge" : self, @" error" : error}];
799799
800800 if ([error userInfo ][RCTJSRawStackTraceKey]) {
801- [self . redBox showErrorMessage: [error localizedDescription ]
802- withRawStack: [error userInfo ][RCTJSRawStackTraceKey]];
801+ [redBox showErrorMessage: [error localizedDescription ]
802+ withRawStack: [error userInfo ][RCTJSRawStackTraceKey]];
803803 }
804804
805805 RCTFatal (error);
@@ -866,63 +866,68 @@ - (void)dispatchBlock:(dispatch_block_t)block
866866
867867- (void )invalidate
868868{
869- if (!_valid ) {
869+ if (_didInvalidate ) {
870870 return ;
871871 }
872872
873873 RCTAssertMainQueue ();
874- RCTAssert (_reactInstance != nil , @" Can't complete invalidation without a react instance " );
874+ RCTLogInfo ( @" Invalidating %@ (parent: %@ , executor: %@ ) " , self, _parentBridge, [ self executorClass ] );
875875
876876 _loading = NO ;
877877 _valid = NO ;
878+ _didInvalidate = YES ;
879+
878880 if ([RCTBridge currentBridge ] == self) {
879881 [RCTBridge setCurrentBridge: nil ];
880882 }
881883
882- // Invalidate modules
883- dispatch_group_t group = dispatch_group_create ();
884- for (RCTModuleData *moduleData in _moduleDataByID) {
885- // Be careful when grabbing an instance here, we don't want to instantiate
886- // any modules just to invalidate them.
887- if (![moduleData hasInstance ]) {
888- continue ;
889- }
884+ // Stop JS instance and message thread
885+ [self ensureOnJavaScriptThread: ^{
886+ [self ->_displayLink invalidate ];
887+ self->_displayLink = nil ;
890888
891- if ([moduleData.instance respondsToSelector: @selector (invalidate )]) {
892- dispatch_group_enter (group);
893- [self dispatchBlock: ^{
894- [(id <RCTInvalidating>)moduleData.instance invalidate ];
895- dispatch_group_leave (group);
896- } queue: moduleData.methodQueue];
889+ if (RCTProfileIsProfiling ()) {
890+ RCTProfileUnhookModules (self);
897891 }
898- [moduleData invalidate ];
899- }
900-
901- dispatch_group_notify (group, dispatch_get_main_queue (), ^{
902- [self ensureOnJavaScriptThread: ^{
903- [self ->_displayLink invalidate ];
904- self->_displayLink = nil ;
905892
906- self->_reactInstance .reset ();
907- if (self->_jsMessageThread ) {
908- self->_jsMessageThread ->quitSynchronous ();
909- self->_jsMessageThread .reset ();
893+ // Invalidate modules
894+ // We're on the JS thread (which we'll be suspending soon), so no new calls will be made to native modules after
895+ // this completes. We must ensure all previous calls were dispatched before deallocating the instance (and module
896+ // wrappers) or we may have invalid pointers still in flight.
897+ dispatch_group_t moduleInvalidation = dispatch_group_create ();
898+ for (RCTModuleData *moduleData in self->_moduleDataByID ) {
899+ // Be careful when grabbing an instance here, we don't want to instantiate
900+ // any modules just to invalidate them.
901+ if (![moduleData hasInstance ]) {
902+ continue ;
910903 }
911904
912- if (RCTProfileIsProfiling ()) {
913- RCTProfileUnhookModules (self);
905+ if ([moduleData.instance respondsToSelector: @selector (invalidate )]) {
906+ dispatch_group_enter (moduleInvalidation);
907+ [self dispatchBlock: ^{
908+ [(id <RCTInvalidating>)moduleData.instance invalidate ];
909+ dispatch_group_leave (moduleInvalidation);
910+ } queue: moduleData.methodQueue];
914911 }
912+ [moduleData invalidate ];
913+ }
915914
916- self->_moduleDataByName = nil ;
917- self->_moduleDataByID = nil ;
918- self->_moduleClassesByID = nil ;
919- self->_pendingCalls = nil ;
915+ if (dispatch_group_wait (moduleInvalidation, dispatch_time (DISPATCH_TIME_NOW , 10 * NSEC_PER_SEC ))) {
916+ RCTLogError (@" Timed out waiting for modules to be invalidated" );
917+ }
920918
921- [self ->_jsThread cancel ];
922- self->_jsThread = nil ;
923- CFRunLoopStop (CFRunLoopGetCurrent ());
924- }];
925- });
919+ self->_reactInstance .reset ();
920+ self->_jsMessageThread .reset ();
921+
922+ self->_moduleDataByName = nil ;
923+ self->_moduleDataByID = nil ;
924+ self->_moduleClassesByID = nil ;
925+ self->_pendingCalls = nil ;
926+
927+ [self ->_jsThread cancel ];
928+ self->_jsThread = nil ;
929+ CFRunLoopStop (CFRunLoopGetCurrent ());
930+ }];
926931}
927932
928933- (void )logMessage : (NSString *)message level : (NSString *)level
@@ -1051,7 +1056,6 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
10511056 */
10521057
10531058 RCTProfileBeginFlowEvent ();
1054-
10551059 [self _runAfterLoad: ^{
10561060 RCTProfileEndFlowEvent ();
10571061
@@ -1142,25 +1146,25 @@ - (JSValue *)callFunctionOnModule:(NSString *)module
11421146 if (!_reactInstance) {
11431147 if (error) {
11441148 *error = RCTErrorWithMessage (
1145- @" Attempt to call sync callFunctionOnModule: on uninitialized bridge" );
1149+ @" callFunctionOnModule was called on uninitialized bridge" );
11461150 }
11471151 return nil ;
11481152 } else if (self.executorClass ) {
11491153 if (error) {
11501154 *error = RCTErrorWithMessage (
1151- @" sync callFunctionOnModule: can only be used with JSC executor" );
1155+ @" callFunctionOnModule can only be used with JSC executor" );
11521156 }
11531157 return nil ;
11541158 } else if (!self.valid ) {
11551159 if (error) {
11561160 *error = RCTErrorWithMessage (
1157- @" sync callFunctionOnModule: bridge is no longer valid" );
1161+ @" Bridge is no longer valid" );
11581162 }
11591163 return nil ;
11601164 } else if (self.loading ) {
11611165 if (error) {
11621166 *error = RCTErrorWithMessage (
1163- @" sync callFunctionOnModule: bridge is still loading" );
1167+ @" Bridge is still loading" );
11641168 }
11651169 return nil ;
11661170 }
0 commit comments