diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index aa2ef7ee91..7143041407 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -610,7 +610,6 @@ static id _latestJSExecutor; selector:@selector(reload) name:RCTReloadNotification object:nil]; - ; } }]; } @@ -619,6 +618,8 @@ static id _latestJSExecutor; - (void)bindKeys { #if TARGET_IPHONE_SIMULATOR + __weak RCTBridge *weakSelf = self; + // Workaround around the first cmd+r not working: http://openradar.appspot.com/19613391 // You can register just the cmd key and do nothing. This will trigger the bug and cmd+r // will work like a charm! @@ -627,27 +628,33 @@ static id _latestJSExecutor; action:^(UIKeyCommand *command) { // Do nothing }]; - [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:^(UIKeyCommand *command) { - [self reload]; + [weakSelf reload]; }]; [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n" modifierFlags:UIKeyModifierCommand action:^(UIKeyCommand *command) { - _executorClass = Nil; - [self reload]; + RCTBridge *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_executorClass = Nil; + [strongSelf reload]; }]; - [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand action:^(UIKeyCommand *command) { - _executorClass = NSClassFromString(@"RCTWebSocketExecutor"); - if (!_executorClass) { + RCTBridge *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_executorClass = NSClassFromString(@"RCTWebSocketExecutor"); + if (!strongSelf->_executorClass) { RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?"); } - [self reload]; + [strongSelf reload]; }]; #endif } @@ -662,7 +669,7 @@ static id _latestJSExecutor; - (void)dealloc { - RCTAssert(!self.valid, @"must call -invalidate before -dealloc"); + [self invalidate]; } #pragma mark - RCTInvalidating @@ -674,6 +681,15 @@ static id _latestJSExecutor; - (void)invalidate { + if (!self.isValid && _modulesByID == nil) { + return; + } + + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES]; + return; + } + [[NSNotificationCenter defaultCenter] removeObserver:self]; // Wait for queued methods to finish diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 2134dc2e5d..3afac6daad 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -31,7 +31,7 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response); * will be set automatically by the bridge when it initializes the module. * To implement this in your module, just add @synthesize bridge = _bridge; */ -@property (nonatomic, strong) RCTBridge *bridge; +@property (nonatomic, weak) RCTBridge *bridge; /** * The module name exposed to JS. If omitted, this will be inferred diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 1d61946b97..205fd79da0 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -24,7 +24,7 @@ */ @implementation RCTJavaScriptLoader { - RCTBridge *_bridge; + __weak RCTBridge *_bridge; } /** diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 6c15e509cd..9743ad4b29 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -92,19 +92,6 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification"; - (void)setUp { if (!_registered) { - /** - * Every root view that is created must have a unique react tag. - * Numbering of these tags goes from 1, 11, 21, 31, etc - * - * NOTE: Since the bridge persists, the RootViews might be reused, so now - * the react tag is assigned every time we load new content. - */ - _contentView = [[UIView alloc] init]; - _contentView.reactTag = [_bridge.uiManager allocateRootTag]; - _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge]; - [_contentView addGestureRecognizer:_touchHandler]; - [self addSubview:_contentView]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload) name:RCTReloadViewsNotification @@ -122,9 +109,9 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification"; - (void)tearDown { + [[NSNotificationCenter defaultCenter] removeObserver:self]; if (_registered) { _registered = NO; - [[NSNotificationCenter defaultCenter] removeObserver:self]; [_contentView removeGestureRecognizer:_touchHandler]; [_contentView removeFromSuperview]; [_touchHandler invalidate]; @@ -174,6 +161,19 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification"; { dispatch_async(dispatch_get_main_queue(), ^{ _registered = YES; + /** + * Every root view that is created must have a unique react tag. + * Numbering of these tags goes from 1, 11, 21, 31, etc + * + * NOTE: Since the bridge persists, the RootViews might be reused, so now + * the react tag is assigned every time we load new content. + */ + _contentView = [[UIView alloc] initWithFrame:self.bounds]; + _contentView.reactTag = [_bridge.uiManager allocateRootTag]; + _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge]; + [_contentView addGestureRecognizer:_touchHandler]; + [self addSubview:_contentView]; + NSString *moduleName = _moduleName ?: @""; NSDictionary *appParameters = @{ @"rootTag": _contentView.reactTag, @@ -197,7 +197,7 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification"; - (void)setFrame:(CGRect)frame { [super setFrame:frame]; - _contentView.frame = self.bounds; + _contentView.frame = (CGRect){CGPointZero, frame.size}; } - (void)reload diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 6c424ec7fb..bdd969ce4c 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -167,12 +167,13 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) - (void)invalidate { - if ([NSThread currentThread] != _javaScriptThread) { - // Yes, block until done. If we're getting called right before dealloc, it's the only safe option. - [self performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:YES]; - } else if (_context != NULL) { - JSGlobalContextRelease(_context); - _context = NULL; + if (self.isValid) { + if ([NSThread currentThread] != _javaScriptThread) { + [self performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO]; + } else { + JSGlobalContextRelease(_context); + _context = NULL; + } } } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index c21adc1d0e..ce0a5baa98 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -177,7 +177,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio @implementation RCTUIManager { - dispatch_queue_t _shadowQueue; + __weak dispatch_queue_t _shadowQueue; // Root views are only mutated on the shadow queue NSMutableSet *_rootViewTags; @@ -319,7 +319,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) // Register shadow view dispatch_async(_shadowQueue, ^{ - RCTShadowView *shadowView = [[RCTShadowView alloc] init]; shadowView.reactTag = reactTag; shadowView.frame = frame; @@ -934,11 +933,9 @@ static void RCTMeasureLayout(RCTShadowView *view, RCTResponseSenderBlock callback) { if (!view) { - RCTLogError(@"Attempting to measure view that does not exist"); return; } if (!ancestor) { - RCTLogError(@"Attempting to measure relative to ancestor that does not exist"); return; } CGRect result = [view measureLayoutRelativeToAncestor:ancestor]; diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index 32babecc96..14277c615f 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -28,7 +28,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v * allowing the manager (or the views that it manages) to manipulate the view * hierarchy and send events back to the JS context. */ -@property (nonatomic, strong) RCTBridge *bridge; +@property (nonatomic, weak) RCTBridge *bridge; /** * The module name exposed to React JS. If omitted, this will be inferred