Skip to content

Commit dc13115

Browse files
nicklockwoodFacebook Github Bot 6
authored andcommitted
Dispatch module setup asynchronously to avoid blocking main thread when bridge starts
Summary:Initializing native modules can block the main thread for tens of milliseconds when it starts up, making it difficult to instantiate the bridge on demand without causing a performance blip. This diff splits up the initialization of modules so that - although they still happen on the main thread - they don't block the thread continuously. Reviewed By: javache Differential Revision: D2965438 fb-gh-sync-id: 38c9c9d281e4672b5874d68b57d4c60d1d268344 shipit-source-id: 38c9c9d281e4672b5874d68b57d4c60d1d268344
1 parent 8f3e5b1 commit dc13115

13 files changed

Lines changed: 308 additions & 315 deletions

File tree

Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,9 @@ - (void)testImageLoading
4747
return nil;
4848
}];
4949

50-
RCTImageLoader *imageLoader = [RCTImageLoader new];
51-
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil];
50+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil];
5251

53-
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
52+
[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
5453
XCTAssertEqual(progress, 1);
5554
XCTAssertEqual(total, 1);
5655
} completionBlock:^(NSError *loadError, id loadedImage) {
@@ -78,10 +77,9 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority
7877
return nil;
7978
}];
8079

81-
RCTImageLoader *imageLoader = [RCTImageLoader new];
82-
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil];
80+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil];
8381

84-
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
82+
[bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
8583
XCTAssertEqual(progress, 1);
8684
XCTAssertEqual(total, 1);
8785
} completionBlock:^(NSError *loadError, id loadedImage) {
@@ -103,10 +101,9 @@ - (void)testImageDecoding
103101
return nil;
104102
}];
105103

106-
RCTImageLoader *imageLoader = [RCTImageLoader new];
107-
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil];
104+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder]; } launchOptions:nil];
108105

109-
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
106+
RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
110107
XCTAssertEqualObjects(decodedImage, image);
111108
XCTAssertNil(decodeError);
112109
}];
@@ -133,10 +130,9 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority
133130
return nil;
134131
}];
135132

136-
RCTImageLoader *imageLoader = [RCTImageLoader new];
137-
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil];
133+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil];
138134

139-
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
135+
RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
140136
XCTAssertEqualObjects(decodedImage, image);
141137
XCTAssertNil(decodeError);
142138
}];

Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,13 @@
3434
} \
3535
}
3636

37-
// Must be declared before RCTTestCustomSetBridgeModule in order to trigger the
38-
// race condition that we are testing for - namely that the
39-
// RCTDidInitializeModuleNotification for RCTTestViewManager gets sent before
40-
// setBridge: is called on RCTTestCustomSetBridgeModule
4137
@interface RCTTestViewManager : RCTViewManager
4238
@end
4339

4440
@implementation RCTTestViewManager
4541

46-
@synthesize bridge = _bridge;
47-
@synthesize methodQueue = _methodQueue;
48-
4942
RCT_EXPORT_MODULE()
5043

51-
- (void)setBridge:(RCTBridge *)bridge
52-
{
53-
_bridge = bridge;
54-
(void)[_bridge uiManager]; // Needed to trigger a race condition
55-
}
56-
5744
- (NSArray<NSString *> *)customDirectEventTypes
5845
{
5946
return @[@"foo"];
@@ -112,7 +99,7 @@ - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge
11299

113100
- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge
114101
{
115-
return @[_notificationObserver];
102+
return @[[RCTTestViewManager new], _notificationObserver];
116103
}
117104

118105
- (void)setUp

Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,11 @@ - (void)moduleDidInit:(NSNotification *)note
202202

203203
- (void)testInjectedModulesInitializedDuringBridgeInit
204204
{
205-
XCTAssertTrue(_injectedModuleInitNotificationSent);
206205
XCTAssertEqual(_injectedModule, [_bridge moduleForClass:[RCTTestInjectedModule class]]);
207206
XCTAssertEqual(_injectedModule.bridge, _bridge.batchedBridge);
208207
XCTAssertNotNil(_injectedModule.methodQueue);
208+
RUN_RUNLOOP_WHILE(!_injectedModuleInitNotificationSent);
209+
XCTAssertTrue(_injectedModuleInitNotificationSent);
209210
}
210211

211212
- (void)testCustomInitModuleInitializedAtBridgeStartup
@@ -214,6 +215,8 @@ - (void)testCustomInitModuleInitializedAtBridgeStartup
214215
XCTAssertTrue(_customInitModuleNotificationSent);
215216
RCTTestCustomInitModule *module = [_bridge moduleForClass:[RCTTestCustomInitModule class]];
216217
XCTAssertTrue(module.initializedOnMainThread);
218+
XCTAssertEqual(module.bridge, _bridge.batchedBridge);
219+
XCTAssertNotNil(module.methodQueue);
217220
}
218221

219222
- (void)testCustomSetBridgeModuleInitializedAtBridgeStartup
@@ -222,6 +225,8 @@ - (void)testCustomSetBridgeModuleInitializedAtBridgeStartup
222225
XCTAssertTrue(_customSetBridgeModuleNotificationSent);
223226
RCTTestCustomSetBridgeModule *module = [_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]];
224227
XCTAssertTrue(module.setBridgeOnMainThread);
228+
XCTAssertEqual(module.bridge, _bridge.batchedBridge);
229+
XCTAssertNotNil(module.methodQueue);
225230
}
226231

227232
- (void)testExportConstantsModuleInitializedAtBridgeStartup
@@ -232,6 +237,8 @@ - (void)testExportConstantsModuleInitializedAtBridgeStartup
232237
RUN_RUNLOOP_WHILE(!module.exportedConstants);
233238
XCTAssertTrue(module.exportedConstants);
234239
XCTAssertTrue(module.exportedConstantsOnMainThread);
240+
XCTAssertEqual(module.bridge, _bridge.batchedBridge);
241+
XCTAssertNotNil(module.methodQueue);
235242
}
236243

237244
- (void)testLazyInitModuleNotInitializedDuringBridgeInit

Libraries/Image/RCTImageLoader.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
9191
resizeMode:(RCTResizeMode)resizeMode
9292
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
9393

94+
/**
95+
* Decodes an image without clipping the result to fit.
96+
*/
97+
- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
98+
size:(CGSize)size
99+
scale:(CGFloat)scale
100+
resizeMode:(RCTResizeMode)resizeMode
101+
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
102+
94103
/**
95104
* Get image size, in pixels. This method will do the least work possible to get
96105
* the information, and won't decode the image if it doesn't have to.

Libraries/Image/RCTImageLoader.m

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -59,54 +59,31 @@ - (void)setUp
5959
_maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
6060
_maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 *1024; // 30MB
6161

62-
// Get image loaders and decoders
63-
NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
64-
NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
65-
for (Class moduleClass in _bridge.moduleClasses) {
66-
if ([moduleClass conformsToProtocol:@protocol(RCTImageURLLoader)]) {
67-
[loaders addObject:[_bridge moduleForClass:moduleClass]];
68-
}
69-
if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
70-
[decoders addObject:[_bridge moduleForClass:moduleClass]];
71-
}
72-
}
73-
74-
// Sort loaders in reverse priority order (highest priority first)
75-
[loaders sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
76-
float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
77-
float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
78-
if (priorityA > priorityB) {
79-
return NSOrderedAscending;
80-
} else if (priorityA < priorityB) {
81-
return NSOrderedDescending;
82-
} else {
83-
return NSOrderedSame;
84-
}
85-
}];
86-
87-
// Sort decoders in reverse priority order (highest priority first)
88-
[decoders sortUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
89-
float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
90-
float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
91-
if (priorityA > priorityB) {
92-
return NSOrderedAscending;
93-
} else if (priorityA < priorityB) {
94-
return NSOrderedDescending;
95-
} else {
96-
return NSOrderedSame;
97-
}
98-
}];
99-
100-
_loaders = loaders;
101-
_decoders = decoders;
62+
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
10263
}
10364

10465
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
10566
{
106-
if (!_loaders) {
67+
if (!_maxConcurrentLoadingTasks) {
10768
[self setUp];
10869
}
10970

71+
if (!_loaders) {
72+
// Get loaders, sorted in reverse priority order (highest priority first)
73+
RCTAssert(_bridge, @"Bridge not set");
74+
_loaders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
75+
float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
76+
float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
77+
if (priorityA > priorityB) {
78+
return NSOrderedAscending;
79+
} else if (priorityA < priorityB) {
80+
return NSOrderedDescending;
81+
} else {
82+
return NSOrderedSame;
83+
}
84+
}];
85+
}
86+
11087
if (RCT_DEBUG) {
11188
// Check for handler conflicts
11289
float previousPriority = 0;
@@ -144,10 +121,26 @@ - (void)setUp
144121

145122
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
146123
{
147-
if (!_decoders) {
124+
if (!_maxConcurrentLoadingTasks) {
148125
[self setUp];
149126
}
150127

128+
if (!_decoders) {
129+
// Get decoders, sorted in reverse priority order (highest priority first)
130+
RCTAssert(_bridge, @"Bridge not set");
131+
_decoders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
132+
float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
133+
float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
134+
if (priorityA > priorityB) {
135+
return NSOrderedAscending;
136+
} else if (priorityA < priorityB) {
137+
return NSOrderedDescending;
138+
} else {
139+
return NSOrderedSame;
140+
}
141+
}];
142+
}
143+
151144
if (RCT_DEBUG) {
152145
// Check for handler conflicts
153146
float previousPriority = 0;
@@ -295,7 +288,7 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
295288

296289
// All access to URL cache must be serialized
297290
if (!_URLCacheQueue) {
298-
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
291+
[self setUp];
299292
}
300293
dispatch_async(_URLCacheQueue, ^{
301294

@@ -539,6 +532,9 @@ - (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
539532
completionHandler:completionHandler];
540533
} else {
541534

535+
if (!_URLCacheQueue) {
536+
[self setUp];
537+
}
542538
dispatch_async(_URLCacheQueue, ^{
543539
dispatch_block_t decodeBlock = ^{
544540

Libraries/Network/RCTNetworking.m

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,8 @@ @implementation RCTNetworking
141141
}
142142

143143
if (!_handlers) {
144-
145-
// get handlers
146-
NSMutableArray<id<RCTURLRequestHandler>> *handlers = [NSMutableArray array];
147-
for (Class moduleClass in _bridge.moduleClasses) {
148-
if ([moduleClass conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
149-
[handlers addObject:[_bridge moduleForClass:moduleClass]];
150-
}
151-
}
152-
153-
// Sort handlers in reverse priority order (highest priority first)
154-
[handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
144+
// Get handlers, sorted in reverse priority order (highest priority first)
145+
_handlers = [[_bridge modulesConformingToProtocol:@protocol(RCTURLRequestHandler)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
155146
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
156147
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
157148
if (priorityA > priorityB) {
@@ -162,8 +153,6 @@ @implementation RCTNetworking
162153
return NSOrderedSame;
163154
}
164155
}];
165-
166-
_handlers = handlers;
167156
}
168157

169158
if (RCT_DEBUG) {

0 commit comments

Comments
 (0)