From 72b363d7fc1bf0b9fe5226d5e1a31141ea144614 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 6 Jun 2016 07:57:55 -0700 Subject: [PATCH] Replaced isMainThread checks with a proper test for main queue Summary: As per https://twitter.com/olebegemann/status/738656134731599872, our use of "main thread" to mean "main queue" seems to be unsafe. This diff replaces the `NSThread.isMainQueue` checks with dispatch_get_specific(), which is the recommended approach. I've also replaced all use of "MainThread" terminology with "MainQueue", and taken the opportunity to deprecate the "sync" param of `RCTExecuteOnMainThread()`, which, while we do still use it in a few places, is incredibly unsafe and shouldn't be encouraged. Reviewed By: javache Differential Revision: D3384910 fbshipit-source-id: ea7c216013372267b82eb25a38db5eb4cd46a089 --- .../UIExplorerUnitTests/RCTModuleInitTests.m | 24 +++++++++--------- Libraries/Image/RCTImageLoader.m | 4 +-- Libraries/Image/RCTImageStoreManager.m | 4 +-- Libraries/Image/RCTImageView.m | 12 +++------ Libraries/WebSocket/RCTWebSocketExecutor.m | 2 +- React/Base/RCTAssert.h | 25 +++++++++++++------ React/Base/RCTAssert.m | 2 +- React/Base/RCTBatchedBridge.m | 20 +++++++-------- React/Base/RCTBridge.m | 12 ++++----- React/Base/RCTConvert.m | 2 +- React/Base/RCTKeyCommands.m | 12 ++++----- React/Base/RCTModuleData.h | 2 +- React/Base/RCTModuleData.m | 12 ++++----- React/Base/RCTRootView.m | 10 ++++---- React/Base/RCTUtils.h | 16 +++++++++--- React/Base/RCTUtils.m | 25 ++++++++++++++++++- React/Modules/RCTAppState.m | 2 +- React/Modules/RCTUIManager.m | 16 ++++++------ React/Views/RCTComponentData.m | 2 +- 19 files changed, 121 insertions(+), 83 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m index a1056a81e..bf05e72ba 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m @@ -48,7 +48,7 @@ RCT_EXPORT_MODULE() @interface RCTTestCustomInitModule : NSObject -@property (nonatomic, assign) BOOL initializedOnMainThread; +@property (nonatomic, assign) BOOL initializedOnMainQueue; @end @@ -62,7 +62,7 @@ RCT_EXPORT_MODULE() - (id)init { if ((self = [super init])) { - _initializedOnMainThread = [NSThread isMainThread]; + _initializedOnMainQueue = RCTIsMainQueue(); } return self; } @@ -72,7 +72,7 @@ RCT_EXPORT_MODULE() @interface RCTTestCustomSetBridgeModule : NSObject -@property (nonatomic, assign) BOOL setBridgeOnMainThread; +@property (nonatomic, assign) BOOL setBridgeOnMainQueue; @end @@ -86,7 +86,7 @@ RCT_EXPORT_MODULE() - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; - _setBridgeOnMainThread = [NSThread isMainThread]; + _setBridgeOnMainQueue = RCTIsMainQueue(); } @end @@ -95,7 +95,7 @@ RCT_EXPORT_MODULE() @interface RCTTestExportConstantsModule : NSObject @property (nonatomic, assign) BOOL exportedConstants; -@property (nonatomic, assign) BOOL exportedConstantsOnMainThread; +@property (nonatomic, assign) BOOL exportedConstantsOnMainQueue; @end @@ -109,7 +109,7 @@ RCT_EXPORT_MODULE() - (NSDictionary *)constantsToExport { _exportedConstants = YES; - _exportedConstantsOnMainThread = [NSThread isMainThread]; + _exportedConstantsOnMainQueue = RCTIsMainQueue(); return @{ @"foo": @"bar" }; } @@ -137,7 +137,7 @@ RCT_EXPORT_MODULE() BOOL _customSetBridgeModuleNotificationSent; BOOL _exportConstantsModuleNotificationSent; BOOL _lazyInitModuleNotificationSent; - BOOL _lazyInitModuleNotificationSentOnMainThread; + BOOL _lazyInitModuleNotificationSentOnMainQueue; BOOL _viewManagerModuleNotificationSent; RCTTestInjectedModule *_injectedModule; } @@ -196,7 +196,7 @@ RCT_EXPORT_MODULE() _exportConstantsModuleNotificationSent = YES; } else if ([module isKindOfClass:[RCTLazyInitModule class]]) { _lazyInitModuleNotificationSent = YES; - _lazyInitModuleNotificationSentOnMainThread = [NSThread isMainThread]; + _lazyInitModuleNotificationSentOnMainQueue = RCTIsMainQueue(); } } @@ -214,7 +214,7 @@ RCT_EXPORT_MODULE() RUN_RUNLOOP_WHILE(!_customInitModuleNotificationSent); XCTAssertTrue(_customInitModuleNotificationSent); RCTTestCustomInitModule *module = [_bridge moduleForClass:[RCTTestCustomInitModule class]]; - XCTAssertTrue(module.initializedOnMainThread); + XCTAssertTrue(module.initializedOnMainQueue); XCTAssertEqual(module.bridge, _bridge.batchedBridge); XCTAssertNotNil(module.methodQueue); } @@ -230,7 +230,7 @@ RCT_EXPORT_MODULE() RUN_RUNLOOP_WHILE(!module); XCTAssertTrue(_customSetBridgeModuleNotificationSent); - XCTAssertFalse(module.setBridgeOnMainThread); + XCTAssertFalse(module.setBridgeOnMainQueue); XCTAssertEqual(module.bridge, _bridge.batchedBridge); XCTAssertNotNil(module.methodQueue); } @@ -242,7 +242,7 @@ RCT_EXPORT_MODULE() RCTTestExportConstantsModule *module = [_bridge moduleForClass:[RCTTestExportConstantsModule class]]; RUN_RUNLOOP_WHILE(!module.exportedConstants); XCTAssertTrue(module.exportedConstants); - XCTAssertTrue(module.exportedConstantsOnMainThread); + XCTAssertTrue(module.exportedConstantsOnMainQueue); XCTAssertEqual(module.bridge, _bridge.batchedBridge); XCTAssertNotNil(module.methodQueue); } @@ -258,7 +258,7 @@ RCT_EXPORT_MODULE() RUN_RUNLOOP_WHILE(!module); XCTAssertTrue(_lazyInitModuleNotificationSent); - XCTAssertFalse(_lazyInitModuleNotificationSentOnMainThread); + XCTAssertFalse(_lazyInitModuleNotificationSentOnMainQueue); XCTAssertNotNil(module); XCTAssertEqual(module.bridge, _bridge.batchedBridge); XCTAssertNotNil(module.methodQueue); diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 5059b2c1c..f0ea4f046 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -293,7 +293,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, __weak RCTImageLoader *weakSelf = self; void (^completionHandler)(NSError *error, id imageOrData) = ^(NSError *error, id imageOrData) { - if ([NSThread isMainThread]) { + if (RCTIsMainQueue()) { // Most loaders do not return on the main thread, so caller is probably not // expecting it, and may do expensive post-processing in the callback @@ -545,7 +545,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, __block volatile uint32_t cancelled = 0; void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) { - if ([NSThread isMainThread]) { + if (RCTIsMainQueue()) { // Most loaders do not return on the main thread, so caller is probably not // expecting it, and may do expensive post-processing in the callback dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m index 4c6441a64..32afcee20 100644 --- a/Libraries/Image/RCTImageStoreManager.m +++ b/Libraries/Image/RCTImageStoreManager.m @@ -191,7 +191,7 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String - (NSString *)storeImage:(UIImage *)image { - RCTAssertMainThread(); + RCTAssertMainQueue(); RCTLogWarn(@"RCTImageStoreManager.storeImage() is deprecated and has poor performance. Use an alternative method instead."); __block NSString *imageTag; dispatch_sync(_methodQueue, ^{ @@ -202,7 +202,7 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String - (UIImage *)imageForTag:(NSString *)imageTag { - RCTAssertMainThread(); + RCTAssertMainQueue(); RCTLogWarn(@"RCTImageStoreManager.imageForTag() is deprecated and has poor performance. Use an alternative method instead."); __block NSData *imageData; dispatch_sync(_methodQueue, ^{ diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index a0d63d35f..3a5864c79 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -263,22 +263,18 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) // Blur on a background thread to avoid blocking interaction dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage *image = RCTBlurredImageWithRadius(loadedImage, blurRadius); - RCTExecuteOnMainThread(^{ + RCTExecuteOnMainQueue(^{ setImageBlock(image); - }, NO); + }); }); } else { // No blur, so try to set the image on the main thread synchronously to minimize image // flashing. (For instance, if this view gets attached to a window, then -didMoveToWindow // calls -reloadImage, and we want to set the image synchronously if possible so that the // image property is set in the same CATransaction that attaches this view to the window.) - if ([NSThread isMainThread]) { + RCTExecuteOnMainQueue(^{ setImageBlock(loadedImage); - } else { - RCTExecuteOnMainThread(^{ - setImageBlock(loadedImage); - }, NO); - } + }); } }]; } else { diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 11adfd8f3..d11505974 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -215,7 +215,7 @@ RCT_EXPORT_MODULE() - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block { - RCTExecuteOnMainThread(block, NO); + RCTExecuteOnMainQueue(block); } - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index 345a32541..977720377 100644 --- a/React/Base/RCTAssert.h +++ b/React/Base/RCTAssert.h @@ -11,6 +11,11 @@ #import "RCTDefines.h" +/* + * Defined in RCTUtils.m + */ +RCT_EXTERN BOOL RCTIsMainQueue(void); + /** * This is the main assert macro that you should use. Asserts should be compiled out * in production builds. You can customize the assert behaviour by setting a custom @@ -58,13 +63,11 @@ RCT_EXTERN NSString *const RCTFatalExceptionName; /** * A block signature to be used for custom assertion handling. */ -typedef void (^RCTAssertFunction)( - NSString *condition, +typedef void (^RCTAssertFunction)(NSString *condition, NSString *fileName, NSNumber *lineNumber, NSString *function, - NSString *message - ); + NSString *message); typedef void (^RCTFatalHandler)(NSError *error); @@ -74,17 +77,23 @@ typedef void (^RCTFatalHandler)(NSError *error); #define RCTAssertParam(name) RCTAssert(name, @"'%s' is a required parameter", #name) /** - * Convenience macro for asserting that we're running on main thread. + * Convenience macro for asserting that we're running on main queue. */ -#define RCTAssertMainThread() RCTAssert([NSThread isMainThread], \ +#define RCTAssertMainQueue() RCTAssert(RCTIsMainQueue(), \ @"This function must be called on the main thread") /** - * Convenience macro for asserting that we're running off the main thread. + * Convenience macro for asserting that we're running off the main queue. */ -#define RCTAssertNotMainThread() RCTAssert(![NSThread isMainThread], \ +#define RCTAssertNotMainQueue() RCTAssert(!RCTIsMainQueue(), \ @"This function must not be called on the main thread") +/** + * Deprecated, do not use + */ +#define RCTAssertMainThread() RCTAssertMainQueue() +#define RCTAssertNotMainThread() RCTAssertNotMainQueue() + /** * These methods get and set the current assert function called by the RCTAssert * macros. You can use these to replace the standard behavior with custom assert diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m index e08e34cc7..d31f85b3f 100644 --- a/React/Base/RCTAssert.m +++ b/React/Base/RCTAssert.m @@ -87,7 +87,7 @@ void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction as NSString *RCTCurrentThreadName(void) { NSThread *thread = [NSThread currentThread]; - NSString *threadName = thread.isMainThread ? @"main" : thread.name; + NSString *threadName = RCTIsMainQueue() || thread.isMainThread ? @"main" : thread.name; if (threadName.length == 0) { const char *label = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL); if (label && strlen(label) > 0) { diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index ac3cd7e80..3314a2d07 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -366,7 +366,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); // Synchronously set up the pre-initialized modules for (RCTModuleData *moduleData in _moduleDataByID) { if (moduleData.hasInstance && - (!moduleData.requiresMainThreadSetup || [NSThread isMainThread])) { + (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) { // Modules that were pre-initialized should ideally be set up before // bridge init has finished, otherwise the caller may try to access the // module directly rather than via `[bridge moduleForClass:]`, which won't @@ -383,10 +383,10 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); // Set up modules that require main thread init or constants export RCTPerformanceLoggerSet(RCTPLNativeModuleMainThread, 0); - NSUInteger modulesOnMainThreadCount = 0; + NSUInteger modulesOnMainQueueCount = 0; for (RCTModuleData *moduleData in _moduleDataByID) { __weak RCTBatchedBridge *weakSelf = self; - if (moduleData.requiresMainThreadSetup || moduleData.hasConstantsToExport) { + if (moduleData.requiresMainQueueSetup || moduleData.hasConstantsToExport) { // Modules that need to be set up on the main thread cannot be initialized // lazily when required without doing a dispatch_sync to the main thread, // which can result in deadlock. To avoid this, we initialize all of these @@ -400,12 +400,12 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); RCTPerformanceLoggerAppendEnd(RCTPLNativeModuleMainThread); } }); - modulesOnMainThreadCount++; + modulesOnMainQueueCount++; } } RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); - RCTPerformanceLoggerSet(RCTPLNativeModuleMainThreadUsesCount, modulesOnMainThreadCount); + RCTPerformanceLoggerSet(RCTPLNativeModuleMainThreadUsesCount, modulesOnMainQueueCount); } - (void)setUpExecutor @@ -509,7 +509,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); - (void)stopLoadingWithError:(NSError *)error { - RCTAssertMainThread(); + RCTAssertMainQueue(); if (!_valid || !_loading) { return; @@ -551,7 +551,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR - (void)setExecutorClass:(Class)executorClass { - RCTAssertMainThread(); + RCTAssertMainQueue(); _parentBridge.executorClass = executorClass; } @@ -599,7 +599,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR return; } - RCTAssertMainThread(); + RCTAssertMainQueue(); RCTAssert(_javaScriptExecutor != nil, @"Can't complete invalidation without a JS executor"); _loading = NO; @@ -1004,7 +1004,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR - (void)startProfiling { - RCTAssertMainThread(); + RCTAssertMainQueue(); [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ RCTProfileInit(self); @@ -1013,7 +1013,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR - (void)stopProfiling:(void (^)(NSData *))callback { - RCTAssertMainThread(); + RCTAssertMainQueue(); [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ RCTProfileEnd(self, ^(NSString *log) { diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 2ca2d7c06..6f3d05b6f 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -117,7 +117,7 @@ static RCTBridge *RCTCurrentBridgeInstance = nil; _delegate = delegate; _launchOptions = [launchOptions copy]; [self setUp]; - RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO); + RCTExecuteOnMainQueue(^{ [self bindKeys]; }); } return self; } @@ -134,7 +134,7 @@ static RCTBridge *RCTCurrentBridgeInstance = nil; _moduleProvider = block; _launchOptions = [launchOptions copy]; [self setUp]; - RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO); + RCTExecuteOnMainQueue(^{ [self bindKeys]; }); } return self; } @@ -145,7 +145,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) { /** * This runs only on the main thread, but crashes the subclass - * RCTAssertMainThread(); + * RCTAssertMainQueue(); */ [[NSNotificationCenter defaultCenter] removeObserver:self]; [self invalidate]; @@ -153,7 +153,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (void)bindKeys { - RCTAssertMainThread(); + RCTAssertMainQueue(); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload) @@ -270,9 +270,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) self.batchedBridge = nil; if (batchedBridge) { - RCTExecuteOnMainThread(^{ + RCTExecuteOnMainQueue(^{ [batchedBridge invalidate]; - }, NO); + }); } } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 8c6816e9b..73f41000f 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -884,7 +884,7 @@ RCT_ENUM_CONVERTER(RCTAnimationType, (@{ } __block UIImage *image; - if (![NSThread isMainThread]) { + if (!RCTIsMainQueue()) { // It seems that none of the UIImage loading methods can be guaranteed // thread safe, so we'll pick the lesser of two evils here and block rather // than run the risk of crashing diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index be59c984b..ddfc5d866 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -271,7 +271,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) modifierFlags:(UIKeyModifierFlags)flags action:(void (^)(UIKeyCommand *))block { - RCTAssertMainThread(); + RCTAssertMainQueue(); if (input.length && flags && RCTIsIOS8OrEarlier()) { @@ -296,7 +296,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags { - RCTAssertMainThread(); + RCTAssertMainQueue(); for (RCTKeyCommand *command in _commands.allObjects) { if ([command matchesInput:input flags:flags]) { @@ -309,7 +309,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags { - RCTAssertMainThread(); + RCTAssertMainQueue(); for (RCTKeyCommand *command in _commands) { if ([command matchesInput:input flags:flags]) { @@ -323,7 +323,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) modifierFlags:(UIKeyModifierFlags)flags action:(void (^)(UIKeyCommand *))block { - RCTAssertMainThread(); + RCTAssertMainQueue(); if (input.length && flags && RCTIsIOS8OrEarlier()) { @@ -348,7 +348,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (void)unregisterDoublePressKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags { - RCTAssertMainThread(); + RCTAssertMainQueue(); for (RCTKeyCommand *command in _commands.allObjects) { if ([command matchesInput:input flags:flags]) { @@ -361,7 +361,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags { - RCTAssertMainThread(); + RCTAssertMainQueue(); for (RCTKeyCommand *command in _commands) { if ([command matchesInput:input flags:flags]) { diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h index 7bd297739..c04f71644 100644 --- a/React/Base/RCTModuleData.h +++ b/React/Base/RCTModuleData.h @@ -48,7 +48,7 @@ /** * Returns YES if module instance must be created on the main thread. */ -@property (nonatomic, assign, readonly) BOOL requiresMainThreadSetup; +@property (nonatomic, assign, readonly) BOOL requiresMainQueueSetup; /** * Returns YES if module has constants to export. diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index 09d70b447..c1509bbe6 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -44,7 +44,7 @@ // If a module overrides `init` then we must assume that it expects to be // initialized on the main thread, because it may need to access UIKit. - _requiresMainThreadSetup = !_instance && + _requiresMainQueueSetup = !_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod; // If a module overrides `constantsToExport` then we must assume that it @@ -85,8 +85,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); [_instanceLock lock]; if (!_setupComplete && _bridge.valid) { if (!_instance) { - if (RCT_DEBUG && _requiresMainThreadSetup) { - RCTAssertMainThread(); + if (RCT_DEBUG && _requiresMainQueueSetup) { + RCTAssertMainQueue(); } RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData setUpInstanceAndBridge] [_moduleClass new]", nil); _instance = [_moduleClass new]; @@ -128,13 +128,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); // If we're here, then the module is completely initialized, // except for what finishSetupForInstance does. When the instance // method is called after moduleSetupComplete, - // finishSetupForInstance will run. If _requiresMainThreadSetup + // finishSetupForInstance will run. If _requiresMainQueueSetup // is true, getting the instance will block waiting for the main // thread, which could take a while if the main thread is busy // (I've seen 50ms in testing). So we clear that flag, since // nothing in finishSetupForInstance needs to be run on the main // thread. - _requiresMainThreadSetup = NO; + _requiresMainQueueSetup = NO; } } @@ -208,7 +208,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); { if (!_setupComplete) { RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, [NSString stringWithFormat:@"[RCTModuleData instanceForClass:%@]", _moduleClass], nil); - if (_requiresMainThreadSetup) { + if (_requiresMainQueueSetup) { // The chances of deadlock here are low, because module init very rarely // calls out to other threads, however we can't control when a module might // get accessed by client code during bridge setup, and a very low risk of diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 30084e4ee..cff7861a1 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -58,7 +58,7 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotificat moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties { - RCTAssertMainThread(); + RCTAssertMainQueue(); RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView"); RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView"); @@ -175,7 +175,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (NSNumber *)reactTag { - RCTAssertMainThread(); + RCTAssertMainQueue(); if (!super.reactTag) { /** * Every root view that is created must have a unique react tag. @@ -191,14 +191,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (void)bridgeDidReload { - RCTAssertMainThread(); + RCTAssertMainQueue(); // Clear the reactTag so it can be re-assigned self.reactTag = nil; } - (void)javaScriptDidLoad:(NSNotification *)notification { - RCTAssertMainThread(); + RCTAssertMainQueue(); RCTBridge *bridge = notification.userInfo[@"bridge"]; [self bundleFinishedLoading:bridge]; } @@ -250,7 +250,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (void)setAppProperties:(NSDictionary *)appProperties { - RCTAssertMainThread(); + RCTAssertMainQueue(); if ([_appProperties isEqualToDictionary:appProperties]) { return; diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index d00464829..04705d383 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -29,9 +29,19 @@ RCT_EXTERN id RCTJSONClean(id object); // Get MD5 hash of a string RCT_EXTERN NSString *RCTMD5Hash(NSString *string); -// Execute the specified block on the main thread. Unlike dispatch_sync/async -// this will not context-switch if we're already running on the main thread. -RCT_EXTERN void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync); +// Check is we are currently on the main queue (not to be confused with +// the main thread, which is not neccesarily the same thing) +// https://twitter.com/olebegemann/status/738656134731599872 +RCT_EXTERN BOOL RCTIsMainQueue(void); + +// Execute the specified block on the main queue. Unlike dispatch_async() +// this will execute immediately if we're already on the main queue. +RCT_EXTERN void RCTExecuteOnMainQueue(dispatch_block_t block); + +// Deprecated - do not use. +RCT_EXTERN void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync) +__deprecated_msg("Use RCTExecuteOnMainQueue instead. RCTExecuteOnMainQueue is " + "async. If you need to use the `sync` option... please don't."); // Get screen metrics in a thread-safe way RCT_EXTERN CGFloat RCTScreenScale(void); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index a477c394c..398f137b0 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -19,6 +19,7 @@ #import #import +#import "RCTAssert.h" #import "RCTLog.h" NSString *const RCTErrorUnspecified = @"EUNSPECIFIED"; @@ -228,9 +229,31 @@ NSString *RCTMD5Hash(NSString *string) ]; } +BOOL RCTIsMainQueue() +{ + static void *mainQueueKey = &mainQueueKey; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dispatch_queue_set_specific(dispatch_get_main_queue(), + mainQueueKey, mainQueueKey, NULL); + }); + return dispatch_get_specific(mainQueueKey) == mainQueueKey; +} + +void RCTExecuteOnMainQueue(dispatch_block_t block) +{ + if (RCTIsMainQueue()) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync) { - if ([NSThread isMainThread]) { + if (RCTIsMainQueue()) { block(); } else if (sync) { dispatch_sync(dispatch_get_main_queue(), ^{ diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m index 3f4c178b3..db9716a7e 100644 --- a/React/Modules/RCTAppState.m +++ b/React/Modules/RCTAppState.m @@ -16,7 +16,7 @@ static NSString *RCTCurrentAppBackgroundState() { - RCTAssertMainThread(); + RCTAssertMainQueue(); static NSDictionary *states; static dispatch_once_t onceToken; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 10ee2152d..168e115dc 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -366,7 +366,7 @@ dispatch_queue_t RCTGetUIManagerQueue(void) - (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility { - RCTAssertMainThread(); + RCTAssertMainQueue(); NSNumber *reactTag = rootView.reactTag; RCTAssert(RCTIsReactRootView(reactTag), @@ -404,13 +404,13 @@ dispatch_queue_t RCTGetUIManagerQueue(void) - (UIView *)viewForReactTag:(NSNumber *)reactTag { - RCTAssertMainThread(); + RCTAssertMainQueue(); return _viewRegistry[reactTag]; } - (void)setFrame:(CGRect)frame forView:(UIView *)view { - RCTAssertMainThread(); + RCTAssertMainQueue(); // The following variable has no meaning if the view is not a react root view RCTRootViewSizeFlexibility sizeFlexibility = RCTRootViewSizeFlexibilityNone; @@ -453,7 +453,7 @@ dispatch_queue_t RCTGetUIManagerQueue(void) - (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view { - RCTAssertMainThread(); + RCTAssertMainQueue(); NSNumber *reactTag = view.reactTag; dispatch_async(RCTGetUIManagerQueue(), ^{ @@ -468,7 +468,7 @@ dispatch_queue_t RCTGetUIManagerQueue(void) - (void)setBackgroundColor:(UIColor *)color forView:(UIView *)view { - RCTAssertMainThread(); + RCTAssertMainQueue(); NSNumber *reactTag = view.reactTag; @@ -534,7 +534,7 @@ dispatch_queue_t RCTGetUIManagerQueue(void) - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView { - RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread"); + RCTAssert(!RCTIsMainQueue(), @"Should be called on shadow queue"); // This is nuanced. In the JS thread, we create a new update buffer // `frameTags`/`frames` that is created/mutated in the JS thread. We access @@ -835,7 +835,7 @@ RCT_EXPORT_METHOD(removeRootView:(nonnull NSNumber *)rootReactTag) [_rootViewTags removeObject:rootReactTag]; [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry){ - RCTAssertMainThread(); + RCTAssertMainQueue(); UIView *rootView = viewRegistry[rootReactTag]; [uiManager _purgeChildren:(NSArray> *)rootView.reactSubviews fromRegistry:(NSMutableDictionary> *)viewRegistry]; @@ -1488,7 +1488,7 @@ RCT_EXPORT_METHOD(clearJSResponder) static NSDictionary *RCTExportedDimensions(BOOL rotateBounds) { - RCTAssertMainThread(); + RCTAssertMainQueue(); // Don't use RCTScreenSize since it the interface orientation doesn't apply to it CGRect screenSize = [[UIScreen mainScreen] bounds]; diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 9259540c1..d22b41e33 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -87,7 +87,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (UIView *)createViewWithTag:(NSNumber *)tag { - RCTAssertMainThread(); + RCTAssertMainQueue(); UIView *view = [self.manager view]; view.reactTag = tag;