diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index b9cee66c3..3e6d749e9 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -769,12 +769,12 @@ static id _latestJSExecutor; } } else { [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification - object:self]; + object:self]; } [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reload) - name:RCTReloadNotification - object:nil]; + selector:@selector(reload) + name:RCTReloadNotification + object:nil]; }]; } } @@ -834,7 +834,7 @@ static id _latestJSExecutor; - (void)dealloc { - RCTAssert(!self.valid, @"must call -invalidate before -dealloc"); + [self invalidate]; } #pragma mark - RCTInvalidating @@ -846,12 +846,16 @@ static id _latestJSExecutor; - (void)invalidate { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + if (!self.isValid && _modulesByID == nil) { + return; + } - // Wait for queued methods to finish - dispatch_sync(self.shadowQueue, ^{ - // Make sure all dispatchers have been executed before continuing - }); + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES]; + return; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self]; // Release executor if (_latestJSExecutor == _javaScriptExecutor) { @@ -860,11 +864,6 @@ static id _latestJSExecutor; [_javaScriptExecutor invalidate]; _javaScriptExecutor = nil; - // Wait for queued methods to finish - dispatch_sync(self.shadowQueue, ^{ - // Make sure all dispatchers have been executed before continuing - }); - // Invalidate modules for (id target in _modulesByID.allObjects) { if ([target respondsToSelector:@selector(invalidate)]) { diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 7fc526e35..e9a5ec41c 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -29,7 +29,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; /** * Place this macro in your class implementation, to automatically register diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 518916c92..ff5371bb4 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 02d7ef447..80df8a44d 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]; @@ -169,6 +156,19 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) { 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, diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 6c424ec7f..c09b301b7 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -17,9 +17,45 @@ #import "RCTLog.h" #import "RCTUtils.h" +@interface RCTJavaScriptContext : NSObject + +@property (nonatomic, assign, readonly) JSGlobalContextRef ctx; + +- (instancetype)initWithJSContext:(JSGlobalContextRef)context; + +@end + +@implementation RCTJavaScriptContext +{ + RCTJavaScriptContext *_self; +} + +- (instancetype)initWithJSContext:(JSGlobalContextRef)context +{ + if ((self = [super init])) { + _ctx = context; + _self = self; + } + return self; +} + +- (BOOL)isValid +{ + return _ctx != NULL; +} + +- (void)invalidate +{ + JSGlobalContextRelease(_ctx); + _ctx = NULL; + _self = nil; +} + +@end + @implementation RCTContextExecutor { - JSGlobalContextRef _context; + RCTJavaScriptContext *_context; NSThread *_javaScriptThread; } @@ -129,21 +165,28 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) { if ((self = [super init])) { _javaScriptThread = javaScriptThread; + __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue: ^{ + RCTContextExecutor *strongSelf = weakSelf; + if (!strongSelf) { + return; + } // Assumes that no other JS tasks are scheduled before. + JSGlobalContextRef ctx; if (context) { - _context = JSGlobalContextRetain(context); + ctx = JSGlobalContextRetain(context); } else { JSContextGroupRef group = JSContextGroupCreate(); - _context = JSGlobalContextCreateInGroup(group, NULL); + ctx = JSGlobalContextCreateInGroup(group, NULL); #if FB_JSC_HACK JSContextGroupBindToCurrentThread(group); #endif JSContextGroupRelease(group); } - [self _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; - [self _addNativeHook:RCTNoop withName:"noop"]; + strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; + [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; + [strongSelf _addNativeHook:RCTNoop withName:"noop"]; }]; } @@ -152,27 +195,24 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) - (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name { - JSObjectRef globalObject = JSContextGetGlobalObject(_context); + JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx); JSStringRef JSName = JSStringCreateWithUTF8CString(name); - JSObjectSetProperty(_context, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context, JSName, hook), kJSPropertyAttributeNone, NULL); + JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL); JSStringRelease(JSName); } - (BOOL)isValid { - return _context != NULL; + return _context.isValid; } - (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) { + [_context performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:NO]; + _context = nil; } } @@ -187,7 +227,12 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) callback:(RCTJavaScriptCallback)onComplete { RCTAssert(onComplete != nil, @"onComplete block should not be nil"); + __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ + RCTContextExecutor *strongSelf = weakSelf; + if (!strongSelf || !strongSelf.isValid) { + return; + } NSError *error; NSString *argsString = RCTJSONStringify(arguments, &error); if (!argsString) { @@ -199,11 +244,11 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) JSValueRef jsError = NULL; JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)execString); - JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, NULL, 0, &jsError); + JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, NULL, 0, &jsError); JSStringRelease(execJSString); if (!result) { - onComplete(nil, RCTNSErrorFromJSError(_context, jsError)); + onComplete(nil, RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError)); return; } @@ -213,8 +258,8 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) id objcValue; // We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds // to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster - if (!JSValueIsNull(_context, result)) { - JSStringRef jsJSONString = JSValueCreateJSONString(_context, result, 0, nil); + if (!JSValueIsNull(strongSelf->_context.ctx, result)) { + JSStringRef jsJSONString = JSValueCreateJSONString(strongSelf->_context.ctx, result, 0, nil); if (jsJSONString) { NSString *objcJSONString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsJSONString); JSStringRelease(jsJSONString); @@ -233,17 +278,22 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) { RCTAssert(url != nil, @"url should not be nil"); RCTAssert(onComplete != nil, @"onComplete block should not be nil"); + __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ + RCTContextExecutor *strongSelf = weakSelf; + if (!strongSelf || !strongSelf.isValid) { + return; + } JSValueRef jsError = NULL; JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script); JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString); - JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, sourceURL, 0, &jsError); + JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, sourceURL, 0, &jsError); JSStringRelease(sourceURL); JSStringRelease(execJSString); NSError *error; if (!result) { - error = RCTNSErrorFromJSError(_context, jsError); + error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError); } onComplete(error); @@ -269,9 +319,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script); #endif + __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ + RCTContextExecutor *strongSelf = weakSelf; + if (!strongSelf || !strongSelf.isValid) { + return; + } JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script); - JSValueRef valueToInject = JSValueMakeFromJSONString(_context, execJSString); + JSValueRef valueToInject = JSValueMakeFromJSONString(strongSelf->_context.ctx, execJSString); JSStringRelease(execJSString); if (!valueToInject) { @@ -283,10 +338,10 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) return; } - JSObjectRef globalObject = JSContextGetGlobalObject(_context); + JSObjectRef globalObject = JSContextGetGlobalObject(strongSelf->_context.ctx); JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName); - JSObjectSetProperty(_context, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL); + JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL); JSStringRelease(JSName); onComplete(nil); }]; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 1fe672d8b..8ce1b0984 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; @@ -322,7 +322,6 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName) // Register shadow view dispatch_async(_shadowQueue, ^{ - RCTShadowView *shadowView = [[RCTShadowView alloc] init]; shadowView.reactTag = reactTag; shadowView.frame = frame; @@ -921,11 +920,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 17d89dff6..92336fa41 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; /** * This method instantiates a native view to be managed by the module. Override