Skip to content

Commit d2934e5

Browse files
nicklockwoodFacebook Github Bot 9
authored andcommitted
Expose UIManager queue via a static function to prevent race conditions
Summary: Having UI modules access the shadowQueue via UIManager.methodQueue is fragile and leads to race conditions in startup, sometimes resulting in an error where the methodQueue is set twice, or not at all. Reviewed By: javache Differential Revision: D3304890 fbshipit-source-id: 7198d28314dbec798877fcaaf17ae017d50157e9
1 parent 1673e00 commit d2934e5

File tree

3 files changed

+31
-23
lines changed

3 files changed

+31
-23
lines changed

React/Modules/RCTUIManager.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
#import "RCTViewManager.h"
1616
#import "RCTRootView.h"
1717

18+
/**
19+
* UIManager queue
20+
*/
21+
RCT_EXTERN dispatch_queue_t RCTGetUIManagerQueue(void);
22+
1823
/**
1924
* Default name for the UIManager queue
2025
*/

React/Modules/RCTUIManager.m

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnim
9191

9292
// Use a custom initialization function rather than implementing `+initialize` so that we can control
9393
// when the initialization code runs. `+initialize` runs immediately before the first message is sent
94-
// to the class which may be too late for us. By this time, we may have missed some
94+
// to the class which may be too late for us. By this time, we may have missed some
9595
// `UIKeyboardWillChangeFrameNotification`s.
9696
+ (void)initializeStatics
9797
{
@@ -208,8 +208,6 @@ - (instancetype)initWithDictionary:(NSDictionary *)config callback:(RCTResponseS
208208

209209
@implementation RCTUIManager
210210
{
211-
dispatch_queue_t _shadowQueue;
212-
213211
// Root views are only mutated on the shadow queue
214212
NSMutableSet<NSNumber *> *_rootViewTags;
215213
NSMutableArray<dispatch_block_t> *_pendingUIBlocks;
@@ -235,7 +233,7 @@ @implementation RCTUIManager
235233
- (void)didReceiveNewContentSizeMultiplier
236234
{
237235
__weak RCTUIManager *weakSelf = self;
238-
dispatch_async(self.methodQueue, ^{
236+
dispatch_async(RCTGetUIManagerQueue(), ^{
239237
RCTUIManager *strongSelf = weakSelf;
240238
if (strongSelf) {
241239
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
@@ -342,22 +340,29 @@ - (void)setBridge:(RCTBridge *)bridge
342340
selector:@selector(interfaceOrientationWillChange:)
343341
name:UIApplicationWillChangeStatusBarOrientationNotification
344342
object:nil];
345-
343+
346344
[RCTAnimation initializeStatics];
347345
}
348346

349-
- (dispatch_queue_t)methodQueue
347+
dispatch_queue_t RCTGetUIManagerQueue(void)
350348
{
351-
if (!_shadowQueue) {
349+
static dispatch_queue_t shadowQueue;
350+
static dispatch_once_t onceToken;
351+
dispatch_once(&onceToken, ^{
352352
if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) {
353353
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
354-
_shadowQueue = dispatch_queue_create(RCTUIManagerQueueName, attr);
354+
shadowQueue = dispatch_queue_create(RCTUIManagerQueueName, attr);
355355
} else {
356-
_shadowQueue = dispatch_queue_create(RCTUIManagerQueueName, DISPATCH_QUEUE_SERIAL);
357-
dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
356+
shadowQueue = dispatch_queue_create(RCTUIManagerQueueName, DISPATCH_QUEUE_SERIAL);
357+
dispatch_set_target_queue(shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
358358
}
359-
}
360-
return _shadowQueue;
359+
});
360+
return shadowQueue;
361+
}
362+
363+
- (dispatch_queue_t)methodQueue
364+
{
365+
return RCTGetUIManagerQueue();
361366
}
362367

363368
- (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
@@ -378,7 +383,7 @@ - (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSize
378383

379384
// Register shadow view
380385
__weak RCTUIManager *weakSelf = self;
381-
dispatch_async(_shadowQueue, ^{
386+
dispatch_async(RCTGetUIManagerQueue(), ^{
382387
RCTUIManager *strongSelf = weakSelf;
383388
if (!_viewRegistry) {
384389
return;
@@ -419,7 +424,7 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view
419424
}
420425

421426
NSNumber *reactTag = view.reactTag;
422-
dispatch_async(_shadowQueue, ^{
427+
dispatch_async(RCTGetUIManagerQueue(), ^{
423428
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
424429
RCTAssert(shadowView != nil, @"Could not locate shadow view with tag #%@", reactTag);
425430

@@ -452,7 +457,7 @@ - (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view
452457
RCTAssertMainThread();
453458

454459
NSNumber *reactTag = view.reactTag;
455-
dispatch_async(_shadowQueue, ^{
460+
dispatch_async(RCTGetUIManagerQueue(), ^{
456461
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
457462
RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag);
458463

@@ -469,7 +474,7 @@ - (void)setBackgroundColor:(UIColor *)color forView:(UIView *)view
469474
NSNumber *reactTag = view.reactTag;
470475

471476
__weak RCTUIManager *weakSelf = self;
472-
dispatch_async(_shadowQueue, ^{
477+
dispatch_async(RCTGetUIManagerQueue(), ^{
473478
RCTUIManager *strongSelf = weakSelf;
474479
if (!_viewRegistry) {
475480
return;
@@ -505,9 +510,9 @@ - (void)_purgeChildren:(NSArray<id<RCTComponent>> *)children
505510

506511
- (void)addUIBlock:(RCTViewManagerUIBlock)block
507512
{
508-
RCTAssertThread(_shadowQueue,
513+
RCTAssertThread(RCTGetUIManagerQueue(),
509514
@"-[RCTUIManager addUIBlock:] should only be called from the "
510-
"UIManager's _shadowQueue (it may be accessed via `bridge.uiManager.methodQueue`)");
515+
"UIManager's queue (get this using `RCTGetUIManagerQueue()`)");
511516

512517
if (!block) {
513518
return;
@@ -1103,7 +1108,8 @@ - (void)_layoutAndMount
11031108

11041109
- (void)flushUIBlocks
11051110
{
1106-
RCTAssertThread(_shadowQueue, @"flushUIBlocks can only be called from the shadow queue");
1111+
RCTAssertThread(RCTGetUIManagerQueue(),
1112+
@"flushUIBlocks can only be called from the shadow queue");
11071113

11081114
// First copy the previous blocks into a temporary variable, then reset the
11091115
// pending blocks to a new array. This guards against mutation while

React/Views/RCTViewManager.m

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,7 @@ @implementation RCTViewManager
5252

5353
- (dispatch_queue_t)methodQueue
5454
{
55-
RCTAssert(_bridge, @"Bridge not set");
56-
RCTAssert(_bridge.uiManager || !_bridge.valid, @"UIManager not initialized");
57-
RCTAssert(_bridge.uiManager.methodQueue || !_bridge.valid, @"UIManager.methodQueue not initialized");
58-
return _bridge.uiManager.methodQueue;
55+
return RCTGetUIManagerQueue();
5956
}
6057

6158
- (UIView *)view

0 commit comments

Comments
 (0)