Skip to content

Commit

Permalink
Refactored module access to allow for lazy loading
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nicklockwood authored and facebook-github-bot-7 committed Nov 25, 2015
1 parent bba71f1 commit 060664f
Show file tree
Hide file tree
Showing 54 changed files with 754 additions and 644 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ @implementation FlexibleSizeExampleView

- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
if ((self = [super initWithFrame:frame])) {
_sizeUpdated = NO;

AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ - (void)setUp

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

_eventName = RCTNormalizeInputEventName(@"sampleEvent");
_body = @{ @"foo": @"bar" };
Expand Down
22 changes: 17 additions & 5 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
#import "RCTUtils.h"
#import "RCTNetworking.h"

#define RUN_RUNLOOP_WHILE(CONDITION) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:5]; \
while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
} \
_Pragma("clang diagnostic pop")

extern BOOL RCTIsGzippedData(NSData *data);

@interface RCTNetworking (Private)
Expand Down Expand Up @@ -61,18 +70,21 @@ - (void)testDontRezipZippedData
- (void)testRequestBodyEncoding
{
NSDictionary *query = @{
@"url": @"http://example.com",
@"method": @"POST",
@"data": @{@"string": @"Hello World"},
@"headers": @{@"Content-Encoding": @"gzip"},
};
@"url": @"http://example.com",
@"method": @"POST",
@"data": @{@"string": @"Hello World"},
@"headers": @{@"Content-Encoding": @"gzip"},
};

RCTNetworking *networker = [RCTNetworking new];
[networker setValue:dispatch_get_main_queue() forKey:@"methodQueue"];
__block NSURLRequest *request = nil;
[networker buildRequest:query completionBlock:^(NSURLRequest *_request) {
request = _request;
}];

RUN_RUNLOOP_WHILE(request == nil);

XCTAssertNotNil(request);
XCTAssertNotNil(request.HTTPBody);
XCTAssertTrue(RCTIsGzippedData(request.HTTPBody));
Expand Down
2 changes: 1 addition & 1 deletion Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ @implementation RCTBridge (RCTAssetsLibraryImageLoader)

- (ALAssetsLibrary *)assetsLibrary
{
return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary];
return [[self moduleForClass:[RCTAssetsLibraryImageLoader class]] assetsLibrary];
}

@end
Expand Down
65 changes: 31 additions & 34 deletions Libraries/CameraRoll/RCTImagePickerManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,6 @@ @implementation RCTImagePickerManager

RCT_EXPORT_MODULE(ImagePickerIOS);

- (instancetype)init
{
if ((self = [super init])) {
_pickers = [NSMutableArray new];
_pickerCallbacks = [NSMutableArray new];
_pickerCancelCallbacks = [NSMutableArray new];
}
return self;
}

- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
Expand All @@ -67,8 +57,6 @@ - (dispatch_queue_t)methodQueue
return;
}

UIViewController *rootViewController = RCTKeyWindow().rootViewController;

UIImagePickerController *imagePicker = [UIImagePickerController new];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
Expand All @@ -77,11 +65,9 @@ - (dispatch_queue_t)methodQueue
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
}

[_pickers addObject:imagePicker];
[_pickerCallbacks addObject:callback];
[_pickerCancelCallbacks addObject:cancelCallback];

[rootViewController presentViewController:imagePicker animated:YES completion:nil];
[self _presentPicker:imagePicker
successCallback:callback
cancelCallback:cancelCallback];
}

RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
Expand All @@ -93,8 +79,6 @@ - (dispatch_queue_t)methodQueue
return;
}

UIViewController *rootViewController = RCTKeyWindow().rootViewController;

UIImagePickerController *imagePicker = [UIImagePickerController new];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
Expand All @@ -109,30 +93,43 @@ - (dispatch_queue_t)methodQueue

imagePicker.mediaTypes = allowedTypes;

[_pickers addObject:imagePicker];
[_pickerCallbacks addObject:callback];
[_pickerCancelCallbacks addObject:cancelCallback];

[rootViewController presentViewController:imagePicker animated:YES completion:nil];
[self _presentPicker:imagePicker
successCallback:callback
cancelCallback:cancelCallback];
}

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
{
NSUInteger index = [_pickers indexOfObject:picker];
RCTResponseSenderBlock callback = _pickerCallbacks[index];
[self _dismissPicker:picker args:@[
[info[UIImagePickerControllerReferenceURL] absoluteString]
]];
}

[_pickers removeObjectAtIndex:index];
[_pickerCallbacks removeObjectAtIndex:index];
[_pickerCancelCallbacks removeObjectAtIndex:index];
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[self _dismissPicker:picker args:nil];
}

UIViewController *rootViewController = RCTKeyWindow().rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];
- (void)_presentPicker:(UIImagePickerController *)imagePicker
successCallback:(RCTResponseSenderBlock)callback
cancelCallback:(RCTResponseSenderBlock)cancelCallback
{
if (!_pickers) {
_pickers = [NSMutableArray new];
_pickerCallbacks = [NSMutableArray new];
_pickerCancelCallbacks = [NSMutableArray new];
}

[_pickers addObject:imagePicker];
[_pickerCallbacks addObject:callback];
[_pickerCancelCallbacks addObject:cancelCallback];

callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]);
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
[rootViewController presentViewController:imagePicker animated:YES completion:nil];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
- (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args
{
NSUInteger index = [_pickers indexOfObject:picker];
RCTResponseSenderBlock callback = _pickerCancelCallbacks[index];
Expand All @@ -144,7 +141,7 @@ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
[rootViewController dismissViewControllerAnimated:YES completion:nil];

callback(@[]);
callback(args ?: @[]);
}

@end
20 changes: 7 additions & 13 deletions Libraries/Geolocation/RCTLocationObserver.m
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,6 @@ @implementation RCTLocationObserver

#pragma mark - Lifecycle

- (instancetype)init
{
if ((self = [super init])) {

_locationManager = [CLLocationManager new];
_locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY;
_locationManager.delegate = self;

_pendingRequests = [NSMutableArray new];
}
return self;
}

- (void)dealloc
{
[_locationManager stopUpdatingLocation];
Expand All @@ -139,6 +126,13 @@ - (dispatch_queue_t)methodQueue

- (void)beginLocationUpdates
{
if (!_locationManager) {
_locationManager = [CLLocationManager new];
_locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY;
_locationManager.delegate = self;
_pendingRequests = [NSMutableArray new];
}

// Request location access permission
if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
Expand Down
46 changes: 26 additions & 20 deletions Libraries/Image/RCTImageLoader.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,17 @@ @implementation RCTImageLoader

RCT_EXPORT_MODULE()

- (void)setBridge:(RCTBridge *)bridge
- (void)setUp
{
// Get image loaders and decoders
NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
[loaders addObject:(id<RCTImageURLLoader>)module];
for (Class moduleClass in _bridge.moduleClasses) {
if ([moduleClass conformsToProtocol:@protocol(RCTImageURLLoader)]) {
[loaders addObject:[_bridge moduleForClass:moduleClass]];
}
if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
[decoders addObject:(id<RCTImageDataDecoder>)module];
if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
[decoders addObject:[_bridge moduleForClass:moduleClass]];
}
}

Expand Down Expand Up @@ -85,17 +85,16 @@ - (void)setBridge:(RCTBridge *)bridge
}
}];

_bridge = bridge;
_loaders = loaders;
_decoders = decoders;
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
_URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
diskCapacity:200 * 1024 * 1024 // 200MB
diskPath:@"React/RCTImageDownloader"];
}

- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
{
if (!_loaders) {
[self setUp];
}

if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
Expand Down Expand Up @@ -133,6 +132,10 @@ - (void)setBridge:(RCTBridge *)bridge

- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
{
if (!_decoders) {
[self setUp];
}

if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
Expand Down Expand Up @@ -212,7 +215,17 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
}

// All access to URL cache must be serialized
if (!_URLCacheQueue) {
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
}
dispatch_async(_URLCacheQueue, ^{

if (!_URLCache) {
_URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
diskCapacity:200 * 1024 * 1024 // 200MB
diskPath:@"React/RCTImageDownloader"];
}

RCTImageLoader *strongSelf = weakSelf;
if (cancelled || !strongSelf) {
return;
Expand Down Expand Up @@ -385,14 +398,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data

- (BOOL)canHandleRequest:(NSURLRequest *)request
{
NSURL *requestURL = request.URL;
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] &&
[(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
return YES;
}
}
return NO;
return [self imageURLLoaderForURL:request.URL] != nil;
}

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

- (RCTImageLoader *)imageLoader
{
return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])];
return [self moduleForClass:[RCTImageLoader class]];
}

@end
17 changes: 7 additions & 10 deletions Libraries/Image/RCTImageStoreManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,6 @@ @implementation RCTImageStoreManager

RCT_EXPORT_MODULE()

- (instancetype)init
{
if ((self = [super init])) {
_store = [NSMutableDictionary new];
_id = 0;
}
return self;
}

- (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
{
dispatch_async(_methodQueue, ^{
Expand All @@ -52,6 +43,12 @@ - (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
- (NSString *)_storeImageData:(NSData *)imageData
{
RCTAssertThread(_methodQueue, @"Must be called on RCTImageStoreManager thread");

if (!_store) {
_store = [NSMutableDictionary new];
_id = 0;
}

NSString *imageTag = [NSString stringWithFormat:@"%@://%tu", RCTImageStoreURLScheme, _id++];
_store[imageTag] = imageData;
return imageTag;
Expand Down Expand Up @@ -225,7 +222,7 @@ @implementation RCTBridge (RCTImageStoreManager)

- (RCTImageStoreManager *)imageStoreManager
{
return self.modules[RCTBridgeModuleNameForClass([RCTImageStoreManager class])];
return [self moduleForClass:[RCTImageStoreManager class]];
}

@end
27 changes: 13 additions & 14 deletions Libraries/LinkingIOS/RCTLinkingManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,20 @@ @implementation RCTLinkingManager

RCT_EXPORT_MODULE()

- (instancetype)init
- (void)setBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleOpenURLNotification:)
name:RCTOpenURLNotification
object:nil];
}
return self;
_bridge = bridge;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleOpenURLNotification:)
name:RCTOpenURLNotification
object:nil];
}

- (NSDictionary<NSString *, id> *)constantsToExport
{
NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)};
}

- (void)dealloc
Expand Down Expand Up @@ -75,10 +80,4 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
callback(@[@(canOpen)]);
}

- (NSDictionary<NSString *, id> *)constantsToExport
{
NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)};
}

@end
Loading

0 comments on commit 060664f

Please sign in to comment.