Skip to content

Commit 060664f

Browse files
nicklockwoodfacebook-github-bot-7
authored andcommitted
Refactored module access to allow for lazy loading
Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
1 parent bba71f1 commit 060664f

54 files changed

Lines changed: 754 additions & 644 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ @implementation FlexibleSizeExampleView
5252

5353
- (instancetype)initWithFrame:(CGRect)frame
5454
{
55-
self = [super initWithFrame:frame];
56-
if (self) {
55+
if ((self = [super initWithFrame:frame])) {
5756
_sizeUpdated = NO;
5857

5958
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ - (void)setUp
6262

6363
_bridge = [OCMockObject mockForClass:[RCTBridge class]];
6464
_eventDispatcher = [RCTEventDispatcher new];
65-
((id<RCTBridgeModule>)_eventDispatcher).bridge = _bridge;
65+
[_eventDispatcher setValue:_bridge forKey:@"bridge"];
6666

6767
_eventName = RCTNormalizeInputEventName(@"sampleEvent");
6868
_body = @{ @"foo": @"bar" };

Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616
#import "RCTUtils.h"
1717
#import "RCTNetworking.h"
1818

19+
#define RUN_RUNLOOP_WHILE(CONDITION) \
20+
_Pragma("clang diagnostic push") \
21+
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
22+
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:5]; \
23+
while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
24+
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
25+
} \
26+
_Pragma("clang diagnostic pop")
27+
1928
extern BOOL RCTIsGzippedData(NSData *data);
2029

2130
@interface RCTNetworking (Private)
@@ -61,18 +70,21 @@ - (void)testDontRezipZippedData
6170
- (void)testRequestBodyEncoding
6271
{
6372
NSDictionary *query = @{
64-
@"url": @"http://example.com",
65-
@"method": @"POST",
66-
@"data": @{@"string": @"Hello World"},
67-
@"headers": @{@"Content-Encoding": @"gzip"},
68-
};
73+
@"url": @"http://example.com",
74+
@"method": @"POST",
75+
@"data": @{@"string": @"Hello World"},
76+
@"headers": @{@"Content-Encoding": @"gzip"},
77+
};
6978

7079
RCTNetworking *networker = [RCTNetworking new];
80+
[networker setValue:dispatch_get_main_queue() forKey:@"methodQueue"];
7181
__block NSURLRequest *request = nil;
7282
[networker buildRequest:query completionBlock:^(NSURLRequest *_request) {
7383
request = _request;
7484
}];
7585

86+
RUN_RUNLOOP_WHILE(request == nil);
87+
7688
XCTAssertNotNil(request);
7789
XCTAssertNotNil(request.HTTPBody);
7890
XCTAssertTrue(RCTIsGzippedData(request.HTTPBody));

Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ @implementation RCTBridge (RCTAssetsLibraryImageLoader)
151151

152152
- (ALAssetsLibrary *)assetsLibrary
153153
{
154-
return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary];
154+
return [[self moduleForClass:[RCTAssetsLibraryImageLoader class]] assetsLibrary];
155155
}
156156

157157
@end

Libraries/CameraRoll/RCTImagePickerManager.m

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,6 @@ @implementation RCTImagePickerManager
3232

3333
RCT_EXPORT_MODULE(ImagePickerIOS);
3434

35-
- (instancetype)init
36-
{
37-
if ((self = [super init])) {
38-
_pickers = [NSMutableArray new];
39-
_pickerCallbacks = [NSMutableArray new];
40-
_pickerCancelCallbacks = [NSMutableArray new];
41-
}
42-
return self;
43-
}
44-
4535
- (dispatch_queue_t)methodQueue
4636
{
4737
return dispatch_get_main_queue();
@@ -67,8 +57,6 @@ - (dispatch_queue_t)methodQueue
6757
return;
6858
}
6959

70-
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
71-
7260
UIImagePickerController *imagePicker = [UIImagePickerController new];
7361
imagePicker.delegate = self;
7462
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
@@ -77,11 +65,9 @@ - (dispatch_queue_t)methodQueue
7765
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
7866
}
7967

80-
[_pickers addObject:imagePicker];
81-
[_pickerCallbacks addObject:callback];
82-
[_pickerCancelCallbacks addObject:cancelCallback];
83-
84-
[rootViewController presentViewController:imagePicker animated:YES completion:nil];
68+
[self _presentPicker:imagePicker
69+
successCallback:callback
70+
cancelCallback:cancelCallback];
8571
}
8672

8773
RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
@@ -93,8 +79,6 @@ - (dispatch_queue_t)methodQueue
9379
return;
9480
}
9581

96-
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
97-
9882
UIImagePickerController *imagePicker = [UIImagePickerController new];
9983
imagePicker.delegate = self;
10084
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
@@ -109,30 +93,43 @@ - (dispatch_queue_t)methodQueue
10993

11094
imagePicker.mediaTypes = allowedTypes;
11195

112-
[_pickers addObject:imagePicker];
113-
[_pickerCallbacks addObject:callback];
114-
[_pickerCancelCallbacks addObject:cancelCallback];
115-
116-
[rootViewController presentViewController:imagePicker animated:YES completion:nil];
96+
[self _presentPicker:imagePicker
97+
successCallback:callback
98+
cancelCallback:cancelCallback];
11799
}
118100

119101
- (void)imagePickerController:(UIImagePickerController *)picker
120102
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
121103
{
122-
NSUInteger index = [_pickers indexOfObject:picker];
123-
RCTResponseSenderBlock callback = _pickerCallbacks[index];
104+
[self _dismissPicker:picker args:@[
105+
[info[UIImagePickerControllerReferenceURL] absoluteString]
106+
]];
107+
}
124108

125-
[_pickers removeObjectAtIndex:index];
126-
[_pickerCallbacks removeObjectAtIndex:index];
127-
[_pickerCancelCallbacks removeObjectAtIndex:index];
109+
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
110+
{
111+
[self _dismissPicker:picker args:nil];
112+
}
128113

129-
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
130-
[rootViewController dismissViewControllerAnimated:YES completion:nil];
114+
- (void)_presentPicker:(UIImagePickerController *)imagePicker
115+
successCallback:(RCTResponseSenderBlock)callback
116+
cancelCallback:(RCTResponseSenderBlock)cancelCallback
117+
{
118+
if (!_pickers) {
119+
_pickers = [NSMutableArray new];
120+
_pickerCallbacks = [NSMutableArray new];
121+
_pickerCancelCallbacks = [NSMutableArray new];
122+
}
123+
124+
[_pickers addObject:imagePicker];
125+
[_pickerCallbacks addObject:callback];
126+
[_pickerCancelCallbacks addObject:cancelCallback];
131127

132-
callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]);
128+
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
129+
[rootViewController presentViewController:imagePicker animated:YES completion:nil];
133130
}
134131

135-
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
132+
- (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args
136133
{
137134
NSUInteger index = [_pickers indexOfObject:picker];
138135
RCTResponseSenderBlock callback = _pickerCancelCallbacks[index];
@@ -144,7 +141,7 @@ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
144141
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
145142
[rootViewController dismissViewControllerAnimated:YES completion:nil];
146143

147-
callback(@[]);
144+
callback(args ?: @[]);
148145
}
149146

150147
@end

Libraries/Geolocation/RCTLocationObserver.m

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,6 @@ @implementation RCTLocationObserver
111111

112112
#pragma mark - Lifecycle
113113

114-
- (instancetype)init
115-
{
116-
if ((self = [super init])) {
117-
118-
_locationManager = [CLLocationManager new];
119-
_locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY;
120-
_locationManager.delegate = self;
121-
122-
_pendingRequests = [NSMutableArray new];
123-
}
124-
return self;
125-
}
126-
127114
- (void)dealloc
128115
{
129116
[_locationManager stopUpdatingLocation];
@@ -139,6 +126,13 @@ - (dispatch_queue_t)methodQueue
139126

140127
- (void)beginLocationUpdates
141128
{
129+
if (!_locationManager) {
130+
_locationManager = [CLLocationManager new];
131+
_locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY;
132+
_locationManager.delegate = self;
133+
_pendingRequests = [NSMutableArray new];
134+
}
135+
142136
// Request location access permission
143137
if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
144138
[_locationManager requestWhenInUseAuthorization];

Libraries/Image/RCTImageLoader.m

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ @implementation RCTImageLoader
4545

4646
RCT_EXPORT_MODULE()
4747

48-
- (void)setBridge:(RCTBridge *)bridge
48+
- (void)setUp
4949
{
5050
// Get image loaders and decoders
5151
NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
5252
NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
53-
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
54-
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
55-
[loaders addObject:(id<RCTImageURLLoader>)module];
53+
for (Class moduleClass in _bridge.moduleClasses) {
54+
if ([moduleClass conformsToProtocol:@protocol(RCTImageURLLoader)]) {
55+
[loaders addObject:[_bridge moduleForClass:moduleClass]];
5656
}
57-
if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
58-
[decoders addObject:(id<RCTImageDataDecoder>)module];
57+
if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
58+
[decoders addObject:[_bridge moduleForClass:moduleClass]];
5959
}
6060
}
6161

@@ -85,17 +85,16 @@ - (void)setBridge:(RCTBridge *)bridge
8585
}
8686
}];
8787

88-
_bridge = bridge;
8988
_loaders = loaders;
9089
_decoders = decoders;
91-
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
92-
_URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
93-
diskCapacity:200 * 1024 * 1024 // 200MB
94-
diskPath:@"React/RCTImageDownloader"];
9590
}
9691

9792
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
9893
{
94+
if (!_loaders) {
95+
[self setUp];
96+
}
97+
9998
if (RCT_DEBUG) {
10099
// Check for handler conflicts
101100
float previousPriority = 0;
@@ -133,6 +132,10 @@ - (void)setBridge:(RCTBridge *)bridge
133132

134133
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
135134
{
135+
if (!_decoders) {
136+
[self setUp];
137+
}
138+
136139
if (RCT_DEBUG) {
137140
// Check for handler conflicts
138141
float previousPriority = 0;
@@ -212,7 +215,17 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
212215
}
213216

214217
// All access to URL cache must be serialized
218+
if (!_URLCacheQueue) {
219+
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
220+
}
215221
dispatch_async(_URLCacheQueue, ^{
222+
223+
if (!_URLCache) {
224+
_URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
225+
diskCapacity:200 * 1024 * 1024 // 200MB
226+
diskPath:@"React/RCTImageDownloader"];
227+
}
228+
216229
RCTImageLoader *strongSelf = weakSelf;
217230
if (cancelled || !strongSelf) {
218231
return;
@@ -385,14 +398,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
385398

386399
- (BOOL)canHandleRequest:(NSURLRequest *)request
387400
{
388-
NSURL *requestURL = request.URL;
389-
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
390-
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] &&
391-
[(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
392-
return YES;
393-
}
394-
}
395-
return NO;
401+
return [self imageURLLoaderForURL:request.URL] != nil;
396402
}
397403

398404
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
@@ -440,7 +446,7 @@ @implementation RCTBridge (RCTImageLoader)
440446

441447
- (RCTImageLoader *)imageLoader
442448
{
443-
return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])];
449+
return [self moduleForClass:[RCTImageLoader class]];
444450
}
445451

446452
@end

Libraries/Image/RCTImageStoreManager.m

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,6 @@ @implementation RCTImageStoreManager
3030

3131
RCT_EXPORT_MODULE()
3232

33-
- (instancetype)init
34-
{
35-
if ((self = [super init])) {
36-
_store = [NSMutableDictionary new];
37-
_id = 0;
38-
}
39-
return self;
40-
}
41-
4233
- (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
4334
{
4435
dispatch_async(_methodQueue, ^{
@@ -52,6 +43,12 @@ - (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
5243
- (NSString *)_storeImageData:(NSData *)imageData
5344
{
5445
RCTAssertThread(_methodQueue, @"Must be called on RCTImageStoreManager thread");
46+
47+
if (!_store) {
48+
_store = [NSMutableDictionary new];
49+
_id = 0;
50+
}
51+
5552
NSString *imageTag = [NSString stringWithFormat:@"%@://%tu", RCTImageStoreURLScheme, _id++];
5653
_store[imageTag] = imageData;
5754
return imageTag;
@@ -225,7 +222,7 @@ @implementation RCTBridge (RCTImageStoreManager)
225222

226223
- (RCTImageStoreManager *)imageStoreManager
227224
{
228-
return self.modules[RCTBridgeModuleNameForClass([RCTImageStoreManager class])];
225+
return [self moduleForClass:[RCTImageStoreManager class]];
229226
}
230227

231228
@end

Libraries/LinkingIOS/RCTLinkingManager.m

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ @implementation RCTLinkingManager
2121

2222
RCT_EXPORT_MODULE()
2323

24-
- (instancetype)init
24+
- (void)setBridge:(RCTBridge *)bridge
2525
{
26-
if ((self = [super init])) {
27-
[[NSNotificationCenter defaultCenter] addObserver:self
28-
selector:@selector(handleOpenURLNotification:)
29-
name:RCTOpenURLNotification
30-
object:nil];
31-
}
32-
return self;
26+
_bridge = bridge;
27+
28+
[[NSNotificationCenter defaultCenter] addObserver:self
29+
selector:@selector(handleOpenURLNotification:)
30+
name:RCTOpenURLNotification
31+
object:nil];
32+
}
33+
34+
- (NSDictionary<NSString *, id> *)constantsToExport
35+
{
36+
NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
37+
return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)};
3338
}
3439

3540
- (void)dealloc
@@ -75,10 +80,4 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
7580
callback(@[@(canOpen)]);
7681
}
7782

78-
- (NSDictionary<NSString *, id> *)constantsToExport
79-
{
80-
NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
81-
return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)};
82-
}
83-
8483
@end

0 commit comments

Comments
 (0)