[ReactNative] Remove bridge retaining cycles

This commit is contained in:
Tadeu Zagallo 2015-04-07 19:18:41 -07:00
Родитель 72390239be
Коммит 6854da9b86
7 изменённых файлов: 52 добавлений и 38 удалений

Просмотреть файл

@ -610,7 +610,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
selector:@selector(reload) selector:@selector(reload)
name:RCTReloadNotification name:RCTReloadNotification
object:nil]; object:nil];
;
} }
}]; }];
} }
@ -619,6 +618,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)bindKeys - (void)bindKeys
{ {
#if TARGET_IPHONE_SIMULATOR #if TARGET_IPHONE_SIMULATOR
__weak RCTBridge *weakSelf = self;
// Workaround around the first cmd+r not working: http://openradar.appspot.com/19613391 // 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 // You can register just the cmd key and do nothing. This will trigger the bug and cmd+r
// will work like a charm! // will work like a charm!
@ -627,27 +628,33 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
action:^(UIKeyCommand *command) { action:^(UIKeyCommand *command) {
// Do nothing // Do nothing
}]; }];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r" [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) { action:^(UIKeyCommand *command) {
[self reload]; [weakSelf reload];
}]; }];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n" [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) { action:^(UIKeyCommand *command) {
_executorClass = Nil; RCTBridge *strongSelf = weakSelf;
[self reload]; if (!strongSelf) {
return;
}
strongSelf->_executorClass = Nil;
[strongSelf reload];
}]; }];
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d" [[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) { action:^(UIKeyCommand *command) {
_executorClass = NSClassFromString(@"RCTWebSocketExecutor"); RCTBridge *strongSelf = weakSelf;
if (!_executorClass) { if (!strongSelf) {
return;
}
strongSelf->_executorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!strongSelf->_executorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?"); RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
} }
[self reload]; [strongSelf reload];
}]; }];
#endif #endif
} }
@ -662,7 +669,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)dealloc - (void)dealloc
{ {
RCTAssert(!self.valid, @"must call -invalidate before -dealloc"); [self invalidate];
} }
#pragma mark - RCTInvalidating #pragma mark - RCTInvalidating
@ -674,6 +681,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)invalidate - (void)invalidate
{ {
if (!self.isValid && _modulesByID == nil) {
return;
}
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
return;
}
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
// Wait for queued methods to finish // Wait for queued methods to finish

Просмотреть файл

@ -31,7 +31,7 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
* will be set automatically by the bridge when it initializes the module. * will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add @synthesize bridge = _bridge; * 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 * The module name exposed to JS. If omitted, this will be inferred

Просмотреть файл

@ -24,7 +24,7 @@
*/ */
@implementation RCTJavaScriptLoader @implementation RCTJavaScriptLoader
{ {
RCTBridge *_bridge; __weak RCTBridge *_bridge;
} }
/** /**

Просмотреть файл

@ -92,19 +92,6 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
- (void)setUp - (void)setUp
{ {
if (!_registered) { 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 [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload) selector:@selector(reload)
name:RCTReloadViewsNotification name:RCTReloadViewsNotification
@ -122,9 +109,9 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
- (void)tearDown - (void)tearDown
{ {
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (_registered) { if (_registered) {
_registered = NO; _registered = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_contentView removeGestureRecognizer:_touchHandler]; [_contentView removeGestureRecognizer:_touchHandler];
[_contentView removeFromSuperview]; [_contentView removeFromSuperview];
[_touchHandler invalidate]; [_touchHandler invalidate];
@ -174,6 +161,19 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
{ {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
_registered = YES; _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 ?: @""; NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{ NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag, @"rootTag": _contentView.reactTag,
@ -197,7 +197,7 @@ NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
- (void)setFrame:(CGRect)frame - (void)setFrame:(CGRect)frame
{ {
[super setFrame:frame]; [super setFrame:frame];
_contentView.frame = self.bounds; _contentView.frame = (CGRect){CGPointZero, frame.size};
} }
- (void)reload - (void)reload

Просмотреть файл

@ -167,13 +167,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
- (void)invalidate - (void)invalidate
{ {
if (self.isValid) {
if ([NSThread currentThread] != _javaScriptThread) { 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:NO];
[self performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:YES]; } else {
} else if (_context != NULL) {
JSGlobalContextRelease(_context); JSGlobalContextRelease(_context);
_context = NULL; _context = NULL;
} }
}
} }
- (void)dealloc - (void)dealloc

Просмотреть файл

@ -177,7 +177,7 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
@implementation RCTUIManager @implementation RCTUIManager
{ {
dispatch_queue_t _shadowQueue; __weak dispatch_queue_t _shadowQueue;
// Root views are only mutated on the shadow queue // Root views are only mutated on the shadow queue
NSMutableSet *_rootViewTags; NSMutableSet *_rootViewTags;
@ -319,7 +319,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
// Register shadow view // Register shadow view
dispatch_async(_shadowQueue, ^{ dispatch_async(_shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init]; RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag; shadowView.reactTag = reactTag;
shadowView.frame = frame; shadowView.frame = frame;
@ -934,11 +933,9 @@ static void RCTMeasureLayout(RCTShadowView *view,
RCTResponseSenderBlock callback) RCTResponseSenderBlock callback)
{ {
if (!view) { if (!view) {
RCTLogError(@"Attempting to measure view that does not exist");
return; return;
} }
if (!ancestor) { if (!ancestor) {
RCTLogError(@"Attempting to measure relative to ancestor that does not exist");
return; return;
} }
CGRect result = [view measureLayoutRelativeToAncestor:ancestor]; CGRect result = [view measureLayoutRelativeToAncestor:ancestor];

Просмотреть файл

@ -28,7 +28,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* allowing the manager (or the views that it manages) to manipulate the view * allowing the manager (or the views that it manages) to manipulate the view
* hierarchy and send events back to the JS context. * 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 * The module name exposed to React JS. If omitted, this will be inferred