diff --git a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m index 134d128cf..ce8992070 100644 --- a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m +++ b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m @@ -52,8 +52,7 @@ RCT_EXPORT_MODULE(); - (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { + if ((self = [super initWithFrame:frame])) { _sizeUpdated = NO; AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m index 5761849d6..e0406d497 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m @@ -62,7 +62,7 @@ _bridge = [OCMockObject mockForClass:[RCTBridge class]]; _eventDispatcher = [RCTEventDispatcher new]; - ((id)_eventDispatcher).bridge = _bridge; + [_eventDispatcher setValue:_bridge forKey:@"bridge"]; _eventName = RCTNormalizeInputEventName(@"sampleEvent"); _body = @{ @"foo": @"bar" }; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m index 69162a764..55b53e48d 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m @@ -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) @@ -61,18 +70,21 @@ extern BOOL RCTIsGzippedData(NSData *data); - (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)); diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m index 9b49742ac..f5cd3cb25 100644 --- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m +++ b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m @@ -151,7 +151,7 @@ RCT_EXPORT_MODULE() - (ALAssetsLibrary *)assetsLibrary { - return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary]; + return [[self moduleForClass:[RCTAssetsLibraryImageLoader class]] assetsLibrary]; } @end diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index 87071863b..0ad9e8e71 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -32,16 +32,6 @@ 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(); @@ -67,8 +57,6 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config return; } - UIViewController *rootViewController = RCTKeyWindow().rootViewController; - UIImagePickerController *imagePicker = [UIImagePickerController new]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; @@ -77,11 +65,9 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config 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 @@ -93,8 +79,6 @@ RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config return; } - UIViewController *rootViewController = RCTKeyWindow().rootViewController; - UIImagePickerController *imagePicker = [UIImagePickerController new]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; @@ -109,30 +93,43 @@ RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config 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 *)info { - NSUInteger index = [_pickers indexOfObject:picker]; - RCTResponseSenderBlock callback = _pickerCallbacks[index]; - - [_pickers removeObjectAtIndex:index]; - [_pickerCallbacks removeObjectAtIndex:index]; - [_pickerCancelCallbacks removeObjectAtIndex:index]; - - UIViewController *rootViewController = RCTKeyWindow().rootViewController; - [rootViewController dismissViewControllerAnimated:YES completion:nil]; - - callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]); + [self _dismissPicker:picker args:@[ + [info[UIImagePickerControllerReferenceURL] absoluteString] + ]]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker +{ + [self _dismissPicker:picker args: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]; + + UIViewController *rootViewController = RCTKeyWindow().rootViewController; + [rootViewController presentViewController:imagePicker animated:YES completion:nil]; +} + +- (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args { NSUInteger index = [_pickers indexOfObject:picker]; RCTResponseSenderBlock callback = _pickerCancelCallbacks[index]; @@ -144,7 +141,7 @@ didFinishPickingMediaWithInfo:(NSDictionary *)info UIViewController *rootViewController = RCTKeyWindow().rootViewController; [rootViewController dismissViewControllerAnimated:YES completion:nil]; - callback(@[]); + callback(args ?: @[]); } @end diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index ab02bd582..9f9d57aa2 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -111,19 +111,6 @@ RCT_EXPORT_MODULE() #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]; @@ -139,6 +126,13 @@ RCT_EXPORT_MODULE() - (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]; diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 7f67b53b7..acee9dbec 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -45,17 +45,17 @@ RCT_EXPORT_MODULE() -- (void)setBridge:(RCTBridge *)bridge +- (void)setUp { // Get image loaders and decoders NSMutableArray> *loaders = [NSMutableArray array]; NSMutableArray> *decoders = [NSMutableArray array]; - for (id module in bridge.modules.allValues) { - if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) { - [loaders addObject:(id)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)module]; + if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) { + [decoders addObject:[_bridge moduleForClass:moduleClass]]; } } @@ -85,17 +85,16 @@ RCT_EXPORT_MODULE() } }]; - _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)imageURLLoaderForURL:(NSURL *)URL { + if (!_loaders) { + [self setUp]; + } + if (RCT_DEBUG) { // Check for handler conflicts float previousPriority = 0; @@ -133,6 +132,10 @@ RCT_EXPORT_MODULE() - (id)imageDataDecoderForData:(NSData *)data { + if (!_decoders) { + [self setUp]; + } + if (RCT_DEBUG) { // Check for handler conflicts float previousPriority = 0; @@ -212,7 +215,17 @@ RCT_EXPORT_MODULE() } // 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; @@ -385,14 +398,7 @@ RCT_EXPORT_MODULE() - (BOOL)canHandleRequest:(NSURLRequest *)request { - NSURL *requestURL = request.URL; - for (id module in _bridge.modules.allValues) { - if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] && - [(id)module canLoadImageURL:requestURL]) { - return YES; - } - } - return NO; + return [self imageURLLoaderForURL:request.URL] != nil; } - (id)sendRequest:(NSURLRequest *)request withDelegate:(id)delegate @@ -440,7 +446,7 @@ RCT_EXPORT_MODULE() - (RCTImageLoader *)imageLoader { - return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])]; + return [self moduleForClass:[RCTImageLoader class]]; } @end diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m index fe080101e..3bda2df35 100644 --- a/Libraries/Image/RCTImageStoreManager.m +++ b/Libraries/Image/RCTImageStoreManager.m @@ -30,15 +30,6 @@ static NSString *const RCTImageStoreURLScheme = @"rct-image-store"; 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, ^{ @@ -52,6 +43,12 @@ RCT_EXPORT_MODULE() - (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; @@ -225,7 +222,7 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String - (RCTImageStoreManager *)imageStoreManager { - return self.modules[RCTBridgeModuleNameForClass([RCTImageStoreManager class])]; + return [self moduleForClass:[RCTImageStoreManager class]]; } @end diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 39fff4eb9..96c01ca2c 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -21,15 +21,20 @@ NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification"; 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 *)constantsToExport +{ + NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey]; + return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)}; } - (void)dealloc @@ -75,10 +80,4 @@ RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL callback(@[@(canOpen)]); } -- (NSDictionary *)constantsToExport -{ - NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey]; - return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)}; -} - @end diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m index f52a20426..1def6d4c7 100644 --- a/Libraries/Network/RCTHTTPRequestHandler.m +++ b/Libraries/Network/RCTHTTPRequestHandler.m @@ -21,26 +21,16 @@ RCT_EXPORT_MODULE() -- (instancetype)init -{ - if ((self = [super init])) { - _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory - valueOptions:NSPointerFunctionsStrongMemory - capacity:0]; - } - return self; -} - - (void)invalidate { [_session invalidateAndCancel]; _session = nil; - _delegates = nil; } - (BOOL)isValid { - return _delegates != nil; + // if session == nil and delegates != nil, we've been invalidated + return _session || !_delegates; } #pragma mark - NSURLRequestHandler @@ -62,12 +52,17 @@ RCT_EXPORT_MODULE() { // Lazy setup if (!_session && [self isValid]) { + NSOperationQueue *callbackQueue = [NSOperationQueue new]; callbackQueue.maxConcurrentOperationCount = 1; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:callbackQueue]; + + _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory + valueOptions:NSPointerFunctionsStrongMemory + capacity:0]; } NSURLSessionDataTask *task = [_session dataTaskWithRequest:request]; diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 749c9e0b6..74b58972c 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -130,36 +130,34 @@ static NSString *RCTGenerateFormBoundary() RCT_EXPORT_MODULE() -- (void)setBridge:(RCTBridge *)bridge -{ - // get handlers - NSMutableArray> *handlers = [NSMutableArray array]; - for (id module in bridge.modules.allValues) { - if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) { - [handlers addObject:(id)module]; - } - } - - // Sort handlers in reverse priority order (highest priority first) - [handlers sortUsingComparator:^NSComparisonResult(id a, id b) { - float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0; - float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0; - if (priorityA > priorityB) { - return NSOrderedAscending; - } else if (priorityA < priorityB) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; - - _bridge = bridge; - _handlers = handlers; - _tasksByRequestID = [NSMutableDictionary new]; -} - - (id)handlerForRequest:(NSURLRequest *)request { + if (!_handlers) { + + // get handlers + NSMutableArray> *handlers = [NSMutableArray array]; + for (Class moduleClass in _bridge.moduleClasses) { + if ([moduleClass conformsToProtocol:@protocol(RCTURLRequestHandler)]) { + [handlers addObject:[_bridge moduleForClass:moduleClass]]; + } + } + + // Sort handlers in reverse priority order (highest priority first) + [handlers sortUsingComparator:^NSComparisonResult(id a, id b) { + float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0; + float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0; + if (priorityA > priorityB) { + return NSOrderedAscending; + } else if (priorityA < priorityB) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; + + _handlers = handlers; + } + if (RCT_DEBUG) { // Check for handler conflicts float previousPriority = 0; @@ -198,6 +196,8 @@ RCT_EXPORT_MODULE() - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query completionBlock:(void (^)(NSURLRequest *request))block { + RCTAssertThread(_methodQueue, @"buildRequest: must be called on method queue"); + NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET"; @@ -222,7 +222,10 @@ RCT_EXPORT_MODULE() [request setValue:(@(request.HTTPBody.length)).description forHTTPHeaderField:@"Content-Length"]; } - block(request); + dispatch_async(_methodQueue, ^{ + block(request); + }); + return (RCTURLRequestCancellationBlock)nil; }]; } @@ -253,6 +256,8 @@ RCT_EXPORT_MODULE() - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary *)query callback: (RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary *result))callback { + RCTAssertThread(_methodQueue, @"processData: must be called on method queue"); + if (!query) { return callback(nil, nil); } @@ -291,6 +296,8 @@ RCT_EXPORT_MODULE() - (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task { + RCTAssertThread(_methodQueue, @"sendData: must be called on method queue"); + if (data.length == 0) { return; } @@ -335,6 +342,8 @@ RCT_EXPORT_MODULE() incrementalUpdates:(BOOL)incrementalUpdates responseSender:(RCTResponseSenderBlock)responseSender { + RCTAssertThread(_methodQueue, @"sendRequest: must be called on method queue"); + __block RCTNetworkTask *task; RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) { @@ -391,6 +400,9 @@ RCT_EXPORT_MODULE() task.uploadProgressBlock = uploadProgressBlock; if (task.requestID) { + if (_tasksByRequestID) { + _tasksByRequestID = [NSMutableDictionary new]; + } _tasksByRequestID[task.requestID] = task; responseSender(@[task.requestID]); } @@ -443,7 +455,7 @@ RCT_EXPORT_METHOD(cancelRequest:(nonnull NSNumber *)requestID) - (RCTNetworking *)networking { - return self.modules[RCTBridgeModuleNameForClass([RCTNetworking class])]; + return [self moduleForClass:[RCTNetworking class]]; } @end diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 751e8ec34..0fda6ee10 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -41,29 +41,11 @@ NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegister @end @implementation RCTPushNotificationManager -{ - NSDictionary *_initialNotification; -} RCT_EXPORT_MODULE() @synthesize bridge = _bridge; -- (instancetype)init -{ - if ((self = [super init])) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleRemoteNotificationReceived:) - name:RCTRemoteNotificationReceived - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleRemoteNotificationsRegistered:) - name:RCTRemoteNotificationsRegistered - object:nil]; - } - return self; -} - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -72,7 +54,21 @@ RCT_EXPORT_MODULE() - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; - _initialNotification = [bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleRemoteNotificationReceived:) + name:RCTRemoteNotificationReceived + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleRemoteNotificationsRegistered:) + name:RCTRemoteNotificationsRegistered + object:nil]; +} + +- (NSDictionary *)constantsToExport +{ + NSDictionary *initialNotification = [_bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy]; + return @{@"initialNotification": RCTNullIfNil(initialNotification)}; } + (void)application:(__unused UIApplication *)application didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings @@ -200,13 +196,6 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) callback(@[permissions]); } -- (NSDictionary *)constantsToExport -{ - return @{ - @"initialNotification": RCTNullIfNil(_initialNotification), - }; -} - RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification) { [RCTSharedApplication() presentLocalNotificationNow:notification]; diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 3976f146e..0692543e7 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -29,14 +29,6 @@ RCT_EXPORT_MODULE() return _bridge.uiManager.methodQueue; } -- (instancetype)init -{ - if ((self = [super init])) { - _snapshotCounter = [NSMutableDictionary new]; - } - return self; -} - RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) { RCTAssert(_controller != nil, @"No snapshot controller configured."); @@ -44,6 +36,9 @@ RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { NSString *testName = NSStringFromSelector(_testSelector); + if (!_snapshotCounter) { + _snapshotCounter = [NSMutableDictionary new]; + } _snapshotCounter[testName] = (@([_snapshotCounter[testName] integerValue] + 1)).stringValue; NSError *error = nil; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 573c7e0a2..6c2e9b5d1 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -112,8 +112,7 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps]; rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices - NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]); - RCTTestModule *testModule = rootView.bridge.modules[testModuleName]; + RCTTestModule *testModule = [rootView.bridge moduleForClass:[RCTTestModule class]]; RCTAssert(_testController != nil, @"_testController should not be nil"); testModule.controller = _testController; testModule.testSelector = test; diff --git a/Libraries/Settings/RCTSettingsManager.h b/Libraries/Settings/RCTSettingsManager.h index 274cc69ae..e0c290880 100644 --- a/Libraries/Settings/RCTSettingsManager.h +++ b/Libraries/Settings/RCTSettingsManager.h @@ -13,6 +13,6 @@ @interface RCTSettingsManager : NSObject -- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults; @end diff --git a/Libraries/Settings/RCTSettingsManager.m b/Libraries/Settings/RCTSettingsManager.m index 3e812e246..2ff4f3b4d 100644 --- a/Libraries/Settings/RCTSettingsManager.m +++ b/Libraries/Settings/RCTSettingsManager.m @@ -24,23 +24,31 @@ RCT_EXPORT_MODULE() -- (instancetype)init -{ - return [self initWithUserDefaults:[NSUserDefaults standardUserDefaults]]; -} - - (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults { - if ((self = [super init])) { + if ((self = [self init])) { _defaults = defaults; + } + return self; +} - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(userDefaultsDidChange:) - name:NSUserDefaultsDidChangeNotification - object:_defaults]; +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + + if (!_defaults) { + _defaults = [NSUserDefaults standardUserDefaults]; } - return self; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(userDefaultsDidChange:) + name:NSUserDefaultsDidChangeNotification + object:_defaults]; +} + +- (NSDictionary *)constantsToExport +{ + return @{@"settings": RCTJSONClean([_defaults dictionaryRepresentation])}; } - (void)dealloc @@ -59,13 +67,6 @@ RCT_EXPORT_MODULE() body:RCTJSONClean([_defaults dictionaryRepresentation])]; } -- (NSDictionary *)constantsToExport -{ - return @{ - @"settings": RCTJSONClean([_defaults dictionaryRepresentation]) - }; -} - /** * Set one or more values in the settings. * TODO: would it be useful to have a callback for when this has completed? diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.h b/Libraries/WebSocket/RCTWebSocketExecutor.h index 2c17541f1..9993cbc5a 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.h +++ b/Libraries/WebSocket/RCTWebSocketExecutor.h @@ -15,7 +15,7 @@ @interface RCTWebSocketExecutor : NSObject -- (instancetype)initWithURL:(NSURL *)URL NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithURL:(NSURL *)URL; @end diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index 41ac0baf2..6ef4b4999 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -36,19 +36,11 @@ typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *RCTGetModuleClasses(void); + (instancetype)currentBridge; + (void)setCurrentBridge:(RCTBridge *)bridge; +@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; + @end @interface RCTBatchedBridge : RCTBridge @@ -63,10 +64,16 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); BOOL _wasBatchActive; __weak id _javaScriptExecutor; NSMutableArray *_pendingCalls; - NSMutableArray *_moduleDataByID; - RCTModuleMap *_modulesByName; + NSMutableDictionary *_moduleDataByName; + NSArray *_moduleDataByID; + NSDictionary> *_modulesByName_DEPRECATED; + NSArray *_moduleClassesByID; CADisplayLink *_jsDisplayLink; NSMutableSet *_frameUpdateObservers; + + // Bridge startup stats (TODO: capture in perf logger) + NSUInteger _syncInitializedModules; + NSUInteger _asyncInitializedModules; } - (instancetype)initWithParentBridge:(RCTBridge *)bridge @@ -86,7 +93,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); _valid = YES; _loading = YES; _pendingCalls = [NSMutableArray new]; - _moduleDataByID = [NSMutableArray new]; _frameUpdateObservers = [NSMutableSet new]; _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)]; @@ -106,6 +112,8 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t initModulesAndLoadSource = dispatch_group_create(); + + // Asynchronously load source code dispatch_group_enter(initModulesAndLoadSource); __weak RCTBatchedBridge *weakSelf = self; __block NSData *sourceCode; @@ -120,9 +128,13 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); dispatch_group_leave(initModulesAndLoadSource); }]; - // Synchronously initialize all native modules + // Synchronously initialize all native modules that cannot be deferred [self initModules]; +#if RCT_DEBUG + _syncInitializedModules = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue]; +#endif + if (RCTProfileIsProfiling()) { // Depends on moduleDataByID being loaded RCTProfileHookModules(self); @@ -132,22 +144,32 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); dispatch_group_enter(initModulesAndLoadSource); dispatch_async(bridgeQueue, ^{ dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create(); + + // Asynchronously initialize the JS executor dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ - [weakSelf setupExecutor]; + [weakSelf setUpExecutor]; }); + // Asynchronously gather the module config dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ if (weakSelf.isValid) { + RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig); config = [weakSelf moduleConfig]; RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig); + +#if RCT_DEBUG + NSInteger total = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue]; + _asyncInitializedModules = total - _syncInitializedModules; +#endif + } }); dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{ - // We're not waiting for this complete to leave the dispatch group, since - // injectJSONConfiguration and executeSourceCode will schedule operations on the - // same queue anyway. + // We're not waiting for this to complete to leave dispatch group, since + // injectJSONConfiguration and executeSourceCode will schedule operations + // on the same queue anyway. RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig); [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) { RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig); @@ -199,6 +221,28 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); } } +- (NSArray *)moduleClasses +{ + if (RCT_DEBUG && self.isValid && _moduleClassesByID == nil) { + RCTLogError(@"Bridge modules have not yet been initialized. You may be " + "trying to access a module too early in the startup procedure."); + } + return _moduleClassesByID; +} + +- (id)moduleForName:(NSString *)moduleName +{ + RCTModuleData *moduleData = _moduleDataByName[moduleName]; + if (RCT_DEBUG) { + Class moduleClass = moduleData.moduleClass; + if (!RCTBridgeModuleClassIsRegistered(moduleClass)) { + RCTLogError(@"Class %@ was not exported. Did you forget to use " + "RCT_EXPORT_MODULE()?", moduleClass); + } + } + return moduleData.instance; +} + - (void)initModules { RCTAssertMainThread(); @@ -220,54 +264,71 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module; } - // Instantiate modules - _moduleDataByID = [NSMutableArray new]; - NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy]; + SEL setBridgeSelector = NSSelectorFromString(@"setBridge:"); + IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)]; + + // Set up moduleData and pre-initialize module instances + NSMutableArray *moduleDataByID = [NSMutableArray new]; + NSMutableDictionary *moduleDataByName = [NSMutableDictionary new]; for (Class moduleClass in RCTGetModuleClasses()) { NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); + id module = preregisteredModules[moduleName]; + if (!module) { + // Check if the module class, or any of its superclasses override init + // or setBridge:. If they do, we assume that they are expecting to be + // initialized when the bridge first loads. + if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod || + [moduleClass instancesRespondToSelector:setBridgeSelector]) { + module = [moduleClass new]; + if (!module) { + module = [NSNull null]; + } + } + } - // Check if module instance has already been registered for this name - id module = modulesByName[moduleName]; + // Check for module name collisions. + // It's OK to have a name collision as long as the second instance is null. + if (module != [NSNull class] && _moduleDataByName[moduleName]) { + RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the name " + "'%@', but name was already registered by class %@", moduleClass, + moduleName, _moduleDataByName[moduleName]); + } - if (module) { - // Preregistered instances takes precedence, no questions asked - if (!preregisteredModules[moduleName]) { - // It's OK to have a name collision as long as the second instance is nil - RCTAssert([moduleClass new] == nil, - @"Attempted to register RCTBridgeModule class %@ for the name " - "'%@', but name was already registered by class %@", moduleClass, - moduleName, [modulesByName[moduleName] class]); - } - } else { - // Module name hasn't been used before, so go ahead and instantiate - module = [moduleClass new]; - } - if (module) { - modulesByName[moduleName] = module; - } + // Instantiate moduleData (TODO: defer this until config generation) + RCTModuleData *moduleData; + if (module) { + if (module != [NSNull null]) { + moduleData = [[RCTModuleData alloc] initWithModuleInstance:module]; + } + } else { + moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass + bridge:self]; + } + if (moduleData) { + moduleDataByName[moduleName] = moduleData; + [moduleDataByID addObject:moduleData]; + } } // Store modules - _modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName]; + _moduleDataByID = [moduleDataByID copy]; + _moduleDataByName = [moduleDataByName copy]; + _moduleClassesByID = [moduleDataByID valueForKey:@"moduleClass"]; /** * The executor is a bridge module, wait for it to be created and set it before * any other module has access to the bridge */ - _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; + _javaScriptExecutor = [self moduleForClass:self.executorClass]; - for (id module in _modulesByName.allValues) { - // Bridge must be set before moduleData is set up, as methodQueue - // initialization requires it (View Managers get their queue by calling - // self.bridge.uiManager.methodQueue) - if ([module respondsToSelector:@selector(setBridge:)]) { - module.bridge = self; + for (RCTModuleData *moduleData in _moduleDataByID) { + [moduleData setBridgeForInstance:self]; + } + + for (RCTModuleData *moduleData in _moduleDataByID) { + if (moduleData.hasInstance) { + [moduleData methodQueue]; // initialize the queue } - - RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor - moduleID:@(_moduleDataByID.count) - instance:module]; - [_moduleDataByID addObject:moduleData]; } [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules @@ -275,7 +336,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); RCTPerformanceLoggerEnd(RCTPLNativeModuleInit); } -- (void)setupExecutor +- (void)setUpExecutor { [_javaScriptExecutor setUp]; } @@ -284,8 +345,8 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); { NSMutableArray *config = [NSMutableArray new]; for (RCTModuleData *moduleData in _moduleDataByID) { - [config addObject:moduleData.config]; - if ([moduleData.instance conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { + [config addObject:RCTNullIfNil(moduleData.config)]; + if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { [_frameUpdateObservers addObject:moduleData]; id observer = (id)moduleData.instance; __weak typeof(self) weakSelf = self; @@ -336,7 +397,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); return; } - RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]]; sourceCodeModule.scriptURL = self.bundleURL; sourceCodeModule.scriptData = sourceCode; @@ -448,13 +509,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR return _valid; } -- (NSDictionary *)modules +- (void)dispatchBlock:(dispatch_block_t)block + queue:(dispatch_queue_t)queue { - if (RCT_DEBUG && self.isValid && _modulesByName == nil) { - RCTLogError(@"Bridge modules have not yet been initialized. You may be " - "trying to access a module too early in the startup procedure."); + if (queue == RCTJSThread) { + [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; + } else if (queue) { + dispatch_async(queue, block); } - return _modulesByName; } #pragma mark - RCTInvalidating @@ -475,17 +537,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR // Invalidate modules dispatch_group_t group = dispatch_group_create(); - for (RCTModuleData *moduleData in _moduleDataByID) { + for (RCTModuleData *moduleData in _moduleDataByName.allValues) { if (moduleData.instance == _javaScriptExecutor) { continue; } if ([moduleData.instance respondsToSelector:@selector(invalidate)]) { - [moduleData dispatchBlock:^{ + dispatch_group_enter(group); + [self dispatchBlock:^{ [(id)moduleData.instance invalidate]; - } dispatchGroup:group]; + dispatch_group_leave(group); + } queue:moduleData.methodQueue]; } - moduleData.queue = nil; + [moduleData invalidate]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ @@ -499,8 +563,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR if (RCTProfileIsProfiling()) { RCTProfileUnhookModules(self); } + _moduleDataByName = nil; _moduleDataByID = nil; - _modulesByName = nil; + _moduleClassesByID = nil; + _modulesByName_DEPRECATED = nil; _frameUpdateObservers = nil; }]; @@ -683,15 +749,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory - capacity:_moduleDataByID.count]; + capacity:_moduleDataByName.count]; [moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) { RCTModuleData *moduleData = _moduleDataByID[moduleID.integerValue]; - if (RCT_DEBUG) { - // verify that class has been registered - (void)_modulesByName[moduleData.name]; - } - dispatch_queue_t queue = moduleData.queue; + dispatch_queue_t queue = moduleData.methodQueue; NSMutableOrderedSet *set = [buckets objectForKey:queue]; if (!set) { set = [NSMutableOrderedSet new]; @@ -739,10 +801,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR { // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case? for (RCTModuleData *moduleData in _moduleDataByID) { - if ([moduleData.instance respondsToSelector:@selector(batchDidComplete)]) { - [moduleData dispatchBlock:^{ + if (moduleData.hasInstance && [moduleData.instance respondsToSelector:@selector(batchDidComplete)]) { + [self dispatchBlock:^{ [moduleData.instance batchDidComplete]; - }]; + } queue:moduleData.methodQueue]; } } } @@ -812,12 +874,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];) RCTProfileBeginFlowEvent(); - [moduleData dispatchBlock:^{ + [self dispatchBlock:^{ RCTProfileEndFlowEvent(); RCT_PROFILE_BEGIN_EVENT(0, name, nil); [observer didUpdateFrame:frameUpdate]; RCT_PROFILE_END_EVENT(0, @"objc_call,fps", nil); - }]; + } queue:moduleData.methodQueue]; } } @@ -851,3 +913,24 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR } @end + +@implementation RCTBatchedBridge(Deprecated) + +- (NSDictionary *)modules +{ + if (!_modulesByName_DEPRECATED) { + // Check classes are set up + [self moduleClasses]; + NSMutableDictionary *modulesByName = [NSMutableDictionary new]; + for (NSString *moduleName in _moduleDataByName) { + id module = [self moduleForName:moduleName]; + if (module) { + modulesByName[moduleName] = module; + } + }; + _modulesByName_DEPRECATED = [modulesByName copy]; + } + return _modulesByName_DEPRECATED; +} + +@end diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index c16786b45..487773f52 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -59,6 +59,11 @@ typedef NSArray> *(^RCTBridgeModuleProviderBlock)(void); */ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); +/** + * This function checks if a class has been registered + */ +RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class); + /** * Async batched bridge used to communicate with the JavaScript application. */ @@ -98,10 +103,18 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args; /** - * DEPRECATED: Do not use. + * Retrieve a bridge module instance by name or class. Note that modules are + * lazily instantiated, so calling these methods for the first time with a given + * module name/class may cause the class to be sychronously instantiated, + * blocking both the calling thread and main thread for a short time. */ -#define RCT_IMPORT_METHOD(module, method) \ - _Pragma("message(\"This macro is no longer required\")") +- (id)moduleForName:(NSString *)moduleName; +- (id)moduleForClass:(Class)moduleClass; + +/** + * All registered bridge module classes. + */ +@property (nonatomic, copy, readonly) NSArray *moduleClasses; /** * URL of the script that was loaded into the bridge. @@ -130,11 +143,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher; -/** - * A dictionary of all registered RCTBridgeModule instances, keyed by moduleName. - */ -@property (nonatomic, copy, readonly) NSDictionary *modules; - /** * The launch options that were used to initialize the bridge. */ @@ -150,14 +158,19 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @property (nonatomic, readonly, getter=isValid) BOOL valid; -/** - * The block passed in the constructor with pre-initialized modules - */ -@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; - /** * Reload the bundle and reset executor & modules. Safe to call from any thread. */ - (void)reload; @end + +/** + * These properties and methods are deprecated and should not be used + */ +@interface RCTBridge (Deprecated) + +@property (nonatomic, copy, readonly) NSDictionary *modules +__deprecated_msg("Use moduleClasses and/or moduleForName: instead"); + +@end diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 15f85c8da..013a54136 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -37,6 +37,7 @@ NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules"; @interface RCTBridge () @property (nonatomic, strong) RCTBatchedBridge *batchedBridge; +@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider; @end @@ -87,9 +88,8 @@ NSString *RCTBridgeModuleNameForClass(Class cls) } /** - * Check if class has been registered + * This function checks if a class has been registered */ -BOOL RCTBridgeModuleClassIsRegistered(Class); BOOL RCTBridgeModuleClassIsRegistered(Class cls) { return [objc_getAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered) ?: @YES boolValue]; @@ -230,9 +230,24 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) #endif } +- (NSArray *)moduleClasses +{ + return _batchedBridge.moduleClasses; +} + +- (id)moduleForName:(NSString *)moduleName +{ + return [_batchedBridge moduleForName:moduleName]; +} + +- (id)moduleForClass:(Class)moduleClass +{ + return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)]; +} + - (RCTEventDispatcher *)eventDispatcher { - return self.modules[RCTBridgeModuleNameForClass([RCTEventDispatcher class])]; + return [self moduleForClass:[RCTEventDispatcher class]]; } - (void)reload @@ -281,10 +296,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) [_batchedBridge logMessage:message level:level]; } -- (NSDictionary *)modules -{ - return _batchedBridge.modules; -} #define RCT_INNER_BRIDGE_ONLY(...) \ - (void)__VA_ARGS__ \ @@ -303,3 +314,12 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module method:(__unused NSString *)method arguments:(__unused NSArray *)args); @end + +@implementation RCTBridge(Deprecated) + +- (NSDictionary *)modules +{ + return _batchedBridge.modules; +} + +@end diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index ee51f5d5b..436b26c80 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -76,7 +76,7 @@ RCT_EXTERN void RCTRegisterModule(Class); \ * will be set automatically by the bridge when it initializes the module. * To implement this in your module, just add `@synthesize bridge = _bridge;` */ -@property (nonatomic, weak) RCTBridge *bridge; +@property (nonatomic, weak, readonly) RCTBridge *bridge; /** * The queue that will be used to call all exported methods. If omitted, this diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index d8d6f3268..b0d2be661 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -97,14 +97,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) RCT_EXPORT_MODULE() -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - _paused = YES; - _eventQueue = [NSMutableDictionary new]; - _eventQueueLock = [NSLock new]; - } - return self; + _bridge = bridge; + _paused = YES; + _eventQueue = [NSMutableDictionary new]; + _eventQueueLock = [NSLock new]; } - (void)setPaused:(BOOL)paused diff --git a/React/Base/RCTKeyboardObserver.m b/React/Base/RCTKeyboardObserver.m index fbe4e4f76..88523a549 100644 --- a/React/Base/RCTKeyboardObserver.m +++ b/React/Base/RCTKeyboardObserver.m @@ -19,25 +19,24 @@ static NSDictionary *RCTParseKeyboardNotification(NSNotification *notification); RCT_EXPORT_MODULE() -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + _bridge = bridge; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; #define ADD_KEYBOARD_HANDLER(NAME, SELECTOR) \ - [nc addObserver:self selector:@selector(SELECTOR:) name:NAME object:nil] + [nc addObserver:self selector:@selector(SELECTOR:) name:NAME object:nil] - ADD_KEYBOARD_HANDLER(UIKeyboardWillShowNotification, keyboardWillShow); - ADD_KEYBOARD_HANDLER(UIKeyboardDidShowNotification, keyboardDidShow); - ADD_KEYBOARD_HANDLER(UIKeyboardWillHideNotification, keyboardWillHide); - ADD_KEYBOARD_HANDLER(UIKeyboardDidHideNotification, keyboardDidHide); - ADD_KEYBOARD_HANDLER(UIKeyboardWillChangeFrameNotification, keyboardWillChangeFrame); - ADD_KEYBOARD_HANDLER(UIKeyboardDidChangeFrameNotification, keyboardDidChangeFrame); + ADD_KEYBOARD_HANDLER(UIKeyboardWillShowNotification, keyboardWillShow); + ADD_KEYBOARD_HANDLER(UIKeyboardDidShowNotification, keyboardDidShow); + ADD_KEYBOARD_HANDLER(UIKeyboardWillHideNotification, keyboardWillHide); + ADD_KEYBOARD_HANDLER(UIKeyboardDidHideNotification, keyboardDidHide); + ADD_KEYBOARD_HANDLER(UIKeyboardWillChangeFrameNotification, keyboardWillChangeFrame); + ADD_KEYBOARD_HANDLER(UIKeyboardDidChangeFrameNotification, keyboardDidChangeFrame); #undef ADD_KEYBOARD_HANDLER - } - return self; } - (void)dealloc diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h index cc3747eae..540059842 100644 --- a/React/Base/RCTModuleData.h +++ b/React/Base/RCTModuleData.h @@ -9,28 +9,58 @@ #import -#import "RCTJavaScriptExecutor.h" +#import "RCTInvalidating.h" @protocol RCTBridgeMethod; +@protocol RCTBridgeModule; +@class RCTBridge; -@interface RCTModuleData : NSObject +@interface RCTModuleData : NSObject -@property (nonatomic, weak, readonly) id javaScriptExecutor; -@property (nonatomic, strong, readonly) NSNumber *moduleID; -@property (nonatomic, strong, readonly) id instance; +- (instancetype)initWithModuleClass:(Class)moduleClass + bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithModuleInstance:(id)instance NS_DESIGNATED_INITIALIZER; + +/** + * Sets the bridge for the module instance. This is only needed when using the + * `initWithModuleID:instance:` constructor. Otherwise, the bridge will be set + * automatically when the module is first accessed. + */ +- (void)setBridgeForInstance:(RCTBridge *)bridge; @property (nonatomic, strong, readonly) Class moduleClass; @property (nonatomic, copy, readonly) NSString *name; + +/** + * Returns the module methods. Note that this will gather the methods the first + * time it is called and then memoize the results. + */ @property (nonatomic, copy, readonly) NSArray> *methods; + +/** + * Returns YES if module instance has already been initialized; NO otherwise. + */ +@property (nonatomic, assign, readonly) BOOL hasInstance; + +/** + * Returns the current module instance. Note that this will init the instance + * if it has not already been created. To check if the module instance exists + * without causing it to be created, use `hasInstance` instead. + */ +@property (nonatomic, strong, readonly) id instance; + +/** + * Returns the module method dispatch queue. Note that this will init both the + * queue and the module itself if they have not already been created. + */ +@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue; + +/** + * Returns the module config. Note that this will init the module if it has + * not already been created. This method can be called on any thread, but will + * block the main thread briefly if the module implements `constantsToExport`. + */ @property (nonatomic, copy, readonly) NSArray *config; -@property (nonatomic, strong) dispatch_queue_t queue; - -- (instancetype)initWithExecutor:(id)javaScriptExecutor - moduleID:(NSNumber *)moduleID - instance:(id)instance NS_DESIGNATED_INITIALIZER; - -- (void)dispatchBlock:(dispatch_block_t)block; -- (void)dispatchBlock:(dispatch_block_t)block dispatchGroup:(dispatch_group_t)group; - @end diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index ddd0384d4..11603850f 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -16,37 +16,75 @@ @implementation RCTModuleData { - NSDictionary *_constants; NSString *_queueName; + __weak RCTBridge *_bridge; } @synthesize methods = _methods; +@synthesize instance = _instance; +@synthesize methodQueue = _methodQueue; -- (instancetype)initWithExecutor:(id)javaScriptExecutor - moduleID:(NSNumber *)moduleID - instance:(id)instance +- (instancetype)initWithModuleClass:(Class)moduleClass + bridge:(RCTBridge *)bridge +{ + if ((self = [super init])) { + _moduleClass = moduleClass; + _bridge = bridge; + } + return self; +} + +- (instancetype)initWithModuleInstance:(id)instance { if ((self = [super init])) { - _javaScriptExecutor = javaScriptExecutor; - _moduleID = moduleID; _instance = instance; _moduleClass = [instance class]; - _name = RCTBridgeModuleNameForClass(_moduleClass); - - // Must be done at init time to ensure it's called on main thread - RCTAssertMainThread(); - if ([_instance respondsToSelector:@selector(constantsToExport)]) { - _constants = [_instance constantsToExport]; - } - - // Must be done at init time due to race conditions - (void)self.queue; } return self; } RCT_NOT_IMPLEMENTED(- (instancetype)init); +- (BOOL)hasInstance +{ + return _instance != nil; +} + +- (id)instance +{ + if (!_instance) { + _instance = [_moduleClass new]; + + // Bridge must be set before methodQueue is set up, as methodQueue + // initialization requires it (View Managers get their queue by calling + // self.bridge.uiManager.methodQueue) + [self setBridgeForInstance:_bridge]; + + // Initialize queue + [self methodQueue]; + } + return _instance; +} + +- (void)setBridgeForInstance:(RCTBridge *)bridge +{ + if ([_instance respondsToSelector:@selector(bridge)]) { + @try { + [(id)_instance setValue:bridge forKey:@"bridge"]; + } + @catch (NSException *exception) { + RCTLogError(@"%@ has no setter or ivar for its bridge, which is not " + "permitted. You must either @synthesize the bridge property, " + "or provide your own setter method.", self.name); + } + } +} + +- (NSString *)name +{ + return RCTBridgeModuleNameForClass(_moduleClass); +} + - (NSArray> *)methods { if (!_methods) { @@ -84,7 +122,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); - (NSArray *)config { - if (_constants.count == 0 && self.methods.count == 0) { + __block NSDictionary *constants; + if (RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport))) { + [self instance]; // Initialize instance + RCTExecuteOnMainThread(^{ + constants = [_instance constantsToExport]; + }, YES); + } + + if (constants.count == 0 && self.methods.count == 0) { return (id)kCFNull; // Nothing to export } @@ -101,9 +147,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); } NSMutableArray *config = [NSMutableArray new]; - [config addObject:_name]; - if (_constants.count) { - [config addObject:_constants]; + [config addObject:self.name]; + if (constants.count) { + [config addObject:constants]; } if (methods) { [config addObject:methods]; @@ -114,53 +160,39 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); return config; } -- (dispatch_queue_t)queue +- (dispatch_queue_t)methodQueue { - if (!_queue) { - BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)]; + if (!_methodQueue) { + BOOL implementsMethodQueue = [self.instance respondsToSelector:@selector(methodQueue)]; if (implementsMethodQueue) { - _queue = _instance.methodQueue; + _methodQueue = _instance.methodQueue; } - if (!_queue) { + if (!_methodQueue) { // Create new queue (store queueName, as it isn't retained by dispatch_queue) - _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", _name]; - _queue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL); + _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", self.name]; + _methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL); // assign it to the module if (implementsMethodQueue) { @try { - [(id)_instance setValue:_queue forKey:@"methodQueue"]; + [(id)_instance setValue:_methodQueue forKey:@"methodQueue"]; } @catch (NSException *exception) { RCTLogError(@"%@ is returning nil for it's methodQueue, which is not " "permitted. You must either return a pre-initialized " "queue, or @synthesize the methodQueue to let the bridge " - "create a queue for you.", _name); + "create a queue for you.", self.name); } } } } - return _queue; + return _methodQueue; } -- (void)dispatchBlock:(dispatch_block_t)block +- (void)invalidate { - [self dispatchBlock:block dispatchGroup:NULL]; -} - -- (void)dispatchBlock:(dispatch_block_t)block - dispatchGroup:(dispatch_group_t)group -{ - if (self.queue == RCTJSThread) { - [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; - } else if (self.queue) { - if (group != NULL) { - dispatch_group_async(group, self.queue, block); - } else { - dispatch_async(self.queue, block); - } - } + _methodQueue = nil; } @end diff --git a/React/Base/RCTModuleMap.h b/React/Base/RCTModuleMap.h deleted file mode 100644 index 48e7c73e6..000000000 --- a/React/Base/RCTModuleMap.h +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -@interface RCTModuleMap : NSDictionary - -- (instancetype)initWithDictionary:(NSDictionary *)modulesByName NS_DESIGNATED_INITIALIZER; - -@end diff --git a/React/Base/RCTModuleMap.m b/React/Base/RCTModuleMap.m deleted file mode 100644 index fef338763..000000000 --- a/React/Base/RCTModuleMap.m +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTModuleMap.h" - -#import "RCTBridge.h" -#import "RCTBridgeModule.h" -#import "RCTDefines.h" -#import "RCTLog.h" - -@implementation RCTModuleMap -{ - NSDictionary *_modulesByName; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)init) -RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:aDecoder) -RCT_NOT_IMPLEMENTED(- (instancetype)initWithObjects:(const id [])objects - forKeys:(const id [])keys - count:(NSUInteger)cnt) - -- (instancetype)initWithDictionary:(NSDictionary *)modulesByName -{ - if ((self = [super init])) { - _modulesByName = [modulesByName copy]; - } - return self; -} - -- (NSUInteger)count -{ - return _modulesByName.count; -} - -//declared in RCTBridge.m -extern BOOL RCTBridgeModuleClassIsRegistered(Class cls); - -- (id)objectForKey:(NSString *)moduleName -{ - id module = _modulesByName[moduleName]; - if (RCT_DEBUG) { - if (module) { - Class moduleClass = [module class]; - if (!RCTBridgeModuleClassIsRegistered(moduleClass)) { - RCTLogError(@"Class %@ was not exported. Did you forget to use " - "RCT_EXPORT_MODULE()?", moduleClass); - } - } else { - Class moduleClass = NSClassFromString(moduleName); - module = _modulesByName[moduleName]; - if (module) { - RCTLogError(@"bridge.modules[name] expects a module name, not a class " - "name. Did you mean to pass '%@' instead?", - RCTBridgeModuleNameForClass(moduleClass)); - } - } - } - return module; -} - -- (NSEnumerator *)keyEnumerator -{ - return [_modulesByName keyEnumerator]; -} - -- (NSArray> *)allValues -{ - // don't perform validation in this case because we only want to error when - // an invalid module is specifically requested - return _modulesByName.allValues; -} - -@end diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m index f099db677..d7f45d693 100644 --- a/React/Base/RCTPerformanceLogger.m +++ b/React/Base/RCTPerformanceLogger.m @@ -79,15 +79,14 @@ RCT_EXPORT_MODULE() @synthesize bridge = _bridge; -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(sendTimespans) - name:RCTContentDidAppearNotification - object:nil]; - } - return self; + _bridge = bridge; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sendTimespans) + name:RCTContentDidAppearNotification + object:nil]; } - (void)dealloc diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 6d399594e..e138da405 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -27,6 +27,10 @@ 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); + // Get screen metrics in a thread-safe way RCT_EXTERN CGFloat RCTScreenScale(void); RCT_EXTERN CGSize RCTScreenSize(void); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 036ecc2bd..8d6f3238d 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -183,18 +183,29 @@ NSString *RCTMD5Hash(NSString *string) ]; } +void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync) +{ + if ([NSThread isMainThread]) { + block(); + } else if (sync) { + dispatch_sync(dispatch_get_main_queue(), ^{ + block(); + }); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + CGFloat RCTScreenScale() { static CGFloat scale; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (![NSThread isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), ^{ - scale = [UIScreen mainScreen].scale; - }); - } else { + RCTExecuteOnMainThread(^{ scale = [UIScreen mainScreen].scale; - } + }, YES); }); return scale; @@ -205,13 +216,9 @@ CGSize RCTScreenSize() static CGSize size; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (![NSThread isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), ^{ - size = [UIScreen mainScreen].bounds.size; - }); - } else { + RCTExecuteOnMainThread(^{ size = [UIScreen mainScreen].bounds.size; - } + }, YES); }); return size; diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m index eb4d31954..fd629cae3 100644 --- a/React/Modules/RCTAccessibilityManager.m +++ b/React/Modules/RCTAccessibilityManager.m @@ -59,6 +59,8 @@ RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { + + // TODO: can this be moved out of the startup path? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNewContentSizeCategory:) name:UIContentSizeCategoryDidChangeNotification @@ -176,7 +178,7 @@ RCT_EXPORT_METHOD(getCurrentVoiceOverState:(RCTResponseSenderBlock)callback - (RCTAccessibilityManager *)accessibilityManager { - return self.modules[RCTBridgeModuleNameForClass([RCTAccessibilityManager class])]; + return [self moduleForClass:[RCTAccessibilityManager class]]; } @end diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index 3bdb59b40..74168866a 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -28,17 +28,6 @@ RCT_EXPORT_MODULE() -- (instancetype)init -{ - if ((self = [super init])) { - _alerts = [NSMutableArray new]; - _alertControllers = [NSMutableArray new]; - _alertCallbacks = [NSMutableArray new]; - _alertButtonKeys = [NSMutableArray new]; - } - return self; -} - - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); @@ -118,6 +107,11 @@ RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args index ++; } + if (!_alerts) { + _alerts = [NSMutableArray new]; + _alertCallbacks = [NSMutableArray new]; + _alertButtonKeys = [NSMutableArray new]; + } [_alerts addObject:alertView]; [_alertCallbacks addObject:callback ?: ^(__unused id unused) {}]; [_alertButtonKeys addObject:buttonKeys]; @@ -175,6 +169,11 @@ RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args }]]; } + if (!_alertControllers) { + _alertControllers = [NSMutableArray new]; + } + [_alertControllers addObject:alertController]; + [presentingController presentViewController:alertController animated:YES completion:nil]; } } diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m index f9ee2cb33..6a22ae626 100644 --- a/React/Modules/RCTAppState.m +++ b/React/Modules/RCTAppState.m @@ -44,27 +44,26 @@ RCT_EXPORT_MODULE() #pragma mark - Lifecycle -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { + _bridge = bridge; - _lastKnownState = RCTCurrentAppBackgroundState(); - - for (NSString *name in @[UIApplicationDidBecomeActiveNotification, - UIApplicationDidEnterBackgroundNotification, - UIApplicationDidFinishLaunchingNotification]) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleAppStateDidChange) - name:name - object:nil]; - } + // Is this thread-safe? + _lastKnownState = RCTCurrentAppBackgroundState(); + for (NSString *name in @[UIApplicationDidBecomeActiveNotification, + UIApplicationDidEnterBackgroundNotification, + UIApplicationDidFinishLaunchingNotification]) { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleMemoryWarning) - name:UIApplicationDidReceiveMemoryWarningNotification + selector:@selector(handleAppStateDidChange) + name:name object:nil]; } - return self; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleMemoryWarning) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; } - (void)handleMemoryWarning diff --git a/React/Modules/RCTDevLoadingView.m b/React/Modules/RCTDevLoadingView.m index a0266b7ce..f522794ab 100644 --- a/React/Modules/RCTDevLoadingView.m +++ b/React/Modules/RCTDevLoadingView.m @@ -34,23 +34,6 @@ RCT_EXPORT_MODULE() isEnabled = enabled; } -- (instancetype)init -{ - if ((self = [super init])) { - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(hide) - name:RCTJavaScriptDidLoadNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(hide) - name:RCTJavaScriptDidFailToLoadNotification - object:nil]; - } - return self; -} - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -59,6 +42,16 @@ RCT_EXPORT_MODULE() - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(hide) + name:RCTJavaScriptDidLoadNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(hide) + name:RCTJavaScriptDidFailToLoadNotification + object:nil]; [self showWithURL:bridge.bundleURL]; } diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index a57c4b643..dd66c900b 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -321,7 +321,7 @@ RCT_EXPORT_MODULE() // Check if live reloading is available _liveReloadURL = nil; - RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + RCTSourceCode *sourceCodeModule = [_bridge moduleForClass:[RCTSourceCode class]]; if (!sourceCodeModule.scriptURL) { if (!sourceCodeModule) { RCTLogWarn(@"RCTSourceCode module not found"); @@ -614,7 +614,7 @@ RCT_EXPORT_METHOD(reload) - (RCTDevMenu *)devMenu { #if RCT_DEV - return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])]; + return [self moduleForClass:[RCTDevMenu class]]; #else return nil; #endif diff --git a/React/Modules/RCTExceptionsManager.h b/React/Modules/RCTExceptionsManager.h index 20ca8d26e..ad5625287 100644 --- a/React/Modules/RCTExceptionsManager.h +++ b/React/Modules/RCTExceptionsManager.h @@ -23,7 +23,7 @@ @interface RCTExceptionsManager : NSObject -- (instancetype)initWithDelegate:(id)delegate NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDelegate:(id)delegate; @property (nonatomic, assign) NSUInteger maxReloadAttempts; diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 9cc5d5722..c536e20a1 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -27,18 +27,12 @@ RCT_EXPORT_MODULE() - (instancetype)initWithDelegate:(id)delegate { - if ((self = [super init])) { + if ((self = [self init])) { _delegate = delegate; - _maxReloadAttempts = 0; } return self; } -- (instancetype)init -{ - return [self initWithDelegate:nil]; -} - RCT_EXPORT_METHOD(reportSoftException:(NSString *)message stack:(NSDictionaryArray *)stack exceptionId:(nonnull NSNumber *)exceptionId) diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index 9f603afd0..6379c1164 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -107,8 +107,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) { if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) { _lastStackTrace = stack; - // message is displayed using UILabel, which is unable to render text of unlimited length, so we truncate it - _lastErrorMessage = [message substringToIndex:MIN(10000, message.length)]; + // message is displayed using UILabel, which is unable to render text of + // unlimited length, so we truncate it + _lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)]; [_stackTraceTableView reloadData]; @@ -326,7 +327,7 @@ RCT_EXPORT_MODULE() - (RCTRedBox *)redBox { - return self.modules[RCTBridgeModuleNameForClass([RCTRedBox class])]; + return [self moduleForClass:[RCTRedBox class]]; } @end diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index 80e40c9aa..dd29a0789 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -46,14 +46,13 @@ RCT_EXPORT_MODULE() @synthesize bridge = _bridge; -- (instancetype)init +- (void)setBridge:(RCTBridge *)bridge { - if ((self = [super init])) { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; - [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil]; - } - return self; + _bridge = bridge; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; + [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil]; } - (void)dealloc diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index a15d25164..51911fdb0 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -179,14 +179,6 @@ static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnim @end -@interface RCTUIManager () - -// NOTE: these are properties so that they can be accessed by unit tests -@property (nonatomic, strong) NSMutableDictionary *shadowViewRegistry; // RCT thread only -@property (nonatomic, strong) NSMutableDictionary *viewRegistry; // Main thread only - -@end - @implementation RCTUIManager { dispatch_queue_t _shadowQueue; @@ -199,6 +191,9 @@ static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnim RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only RCTLayoutAnimation *_layoutAnimation; // Main thread only + NSMutableDictionary *_shadowViewRegistry; // RCT thread only + NSMutableDictionary *_viewRegistry; // Main thread only + // Keyed by viewName NSDictionary *_componentDataByName; @@ -214,31 +209,6 @@ RCT_EXPORT_MODULE() */ extern NSString *RCTBridgeModuleNameForClass(Class cls); -- (instancetype)init -{ - if ((self = [super init])) { - const char *queueName = "com.facebook.React.ShadowQueue"; - - if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) { - dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0); - _shadowQueue = dispatch_queue_create(queueName, attr); - } else { - _shadowQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); - dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); - } - - _shadowViewRegistry = [NSMutableDictionary new]; - _viewRegistry = [NSMutableDictionary new]; - - // Internal resources - _pendingUIBlocks = [NSMutableArray new]; - _rootViewTags = [NSMutableSet new]; - - _bridgeTransactionListeners = [NSMutableSet new]; - } - return self; -} - - (void)didReceiveNewContentSizeMultiplier { __weak RCTUIManager *weakSelf = self; @@ -276,18 +246,45 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); }); } +- (NSMutableDictionary *)shadowViewRegistry +{ + // NOTE: this method only exists so that it can be accessed by unit tests + if (!_shadowViewRegistry) { + _shadowViewRegistry = [NSMutableDictionary new]; + } + return _shadowViewRegistry; +} + +- (NSMutableDictionary *)viewRegistry +{ + // NOTE: this method only exists so that it can be accessed by unit tests + if (!_viewRegistry) { + _viewRegistry = [NSMutableDictionary new]; + } + return _viewRegistry; +} + - (void)setBridge:(RCTBridge *)bridge { RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance"); _bridge = bridge; + _shadowViewRegistry = [NSMutableDictionary new]; + _viewRegistry = [NSMutableDictionary new]; + + // Internal resources + _pendingUIBlocks = [NSMutableArray new]; + _rootViewTags = [NSMutableSet new]; + + _bridgeTransactionListeners = [NSMutableSet new]; // Get view managers from bridge NSMutableDictionary *componentDataByName = [NSMutableDictionary new]; - for (RCTViewManager *manager in _bridge.modules.allValues) { - if ([manager isKindOfClass:[RCTViewManager class]]) { - RCTComponentData *componentData = [[RCTComponentData alloc] initWithManager:manager]; + for (Class moduleClass in _bridge.moduleClasses) { + if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) { + RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass + bridge:_bridge]; componentDataByName[componentData.name] = componentData; } } @@ -302,6 +299,17 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); - (dispatch_queue_t)methodQueue { + if (!_shadowQueue) { + const char *queueName = "com.facebook.React.ShadowQueue"; + + if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) { + dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0); + _shadowQueue = dispatch_queue_create(queueName, attr); + } else { + _shadowQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); + } + } return _shadowQueue; } @@ -1126,12 +1134,11 @@ RCT_EXPORT_METHOD(clearJSResponder) [_componentDataByName enumerateKeysAndObjectsUsingBlock: ^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) { - RCTViewManager *manager = componentData.manager; NSMutableDictionary *constantsNamespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]]; // Add manager class - constantsNamespace[@"Manager"] = RCTBridgeModuleNameForClass([manager class]); + constantsNamespace[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass); // Add native props NSDictionary *viewConfig = [componentData viewConfig]; @@ -1216,7 +1223,7 @@ static UIView *_jsResponder; - (RCTUIManager *)uiManager { - return self.modules[RCTBridgeModuleNameForClass([RCTUIManager class])]; + return [self moduleForClass:[RCTUIManager class]]; } @end diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index 276ae2d4f..fe93b6580 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -30,6 +30,13 @@ NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; #if RCT_DEV +@interface RCTBridge () + +- (void)dispatchBlock:(dispatch_block_t)block + queue:(dispatch_queue_t)queue; + +@end + #pragma mark - Constants NSString const *RCTProfileTraceEvents = @"traceEvents"; @@ -211,7 +218,7 @@ void RCTProfileHookModules(RCTBridge *bridge) RCTProfileHookedModules = YES; for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { - [moduleData dispatchBlock:^{ + [bridge dispatchBlock:^{ Class moduleClass = moduleData.moduleClass; Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); @@ -242,7 +249,7 @@ void RCTProfileHookModules(RCTBridge *bridge) objc_registerClassPair(proxyClass); object_setClass(moduleData.instance, proxyClass); - }]; + } queue:moduleData.methodQueue]; } } diff --git a/React/Profiler/RCTProfileTrampoline-arm.S b/React/Profiler/RCTProfileTrampoline-arm.S index 4835d261a..61c32df92 100644 --- a/React/Profiler/RCTProfileTrampoline-arm.S +++ b/React/Profiler/RCTProfileTrampoline-arm.S @@ -1,3 +1,12 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/Profiler/RCTProfileTrampoline-arm64.S b/React/Profiler/RCTProfileTrampoline-arm64.S index 92ea42a83..435d86a4d 100644 --- a/React/Profiler/RCTProfileTrampoline-arm64.S +++ b/React/Profiler/RCTProfileTrampoline-arm64.S @@ -1,3 +1,12 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/Profiler/RCTProfileTrampoline-i386.S b/React/Profiler/RCTProfileTrampoline-i386.S index 0ccd37b4f..7aed48132 100644 --- a/React/Profiler/RCTProfileTrampoline-i386.S +++ b/React/Profiler/RCTProfileTrampoline-i386.S @@ -1,3 +1,12 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/Profiler/RCTProfileTrampoline-x86_64.S b/React/Profiler/RCTProfileTrampoline-x86_64.S index 9a7a81c85..02fa010a4 100644 --- a/React/Profiler/RCTProfileTrampoline-x86_64.S +++ b/React/Profiler/RCTProfileTrampoline-x86_64.S @@ -1,3 +1,12 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + #include "RCTDefines.h" #include "RCTMacros.h" diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index b04420043..4250901f6 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E21AA5CF210034F82E /* RCTTabBarItem.m */; }; 137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */; }; 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E61AA5CF210034F82E /* RCTTabBarManager.m */; }; - 1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 1385D0331B665AAE000A309B /* RCTModuleMap.m */; }; 13A0C2891B74F71200B29F6F /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */; }; 13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */; }; 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; @@ -48,6 +47,7 @@ 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; }; 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; }; 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; }; + 13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */ = {isa = PBXBuildFile; fileRef = 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */; }; 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 13F17A841B8493E5007D4C75 /* RCTRedBox.m */; }; 14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; }; 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 142014171B32094000CC17BA /* RCTPerformanceLogger.m */; }; @@ -57,7 +57,6 @@ 1450FF871BCFF28A00208362 /* RCTProfileTrampoline-arm.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */; }; 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */; }; 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */; }; - 14BF71801C04793D00C97D0C /* RCTProfileTrampoline-i386.S in Sources */ = {isa = PBXBuildFile; fileRef = 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */; }; 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */; }; 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */; }; 14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */; }; @@ -134,8 +133,6 @@ 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = ""; }; 137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = ""; }; 137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = ""; }; - 1385D0331B665AAE000A309B /* RCTModuleMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMap.m; sourceTree = ""; }; - 1385D0351B6661DB000A309B /* RCTModuleMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTModuleMap.h; sourceTree = ""; }; 13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevLoadingView.h; sourceTree = ""; }; 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevLoadingView.m; sourceTree = ""; }; 13A0C2871B74F71200B29F6F /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevMenu.h; sourceTree = ""; }; @@ -432,7 +429,6 @@ isa = PBXGroup; children = ( 14BF71811C04795500C97D0C /* RCTMacros.h */, - 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */, 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */, 14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */, 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */, @@ -440,6 +436,7 @@ 1450FF811BCFF28A00208362 /* RCTProfile.m */, 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */, 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */, + 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */, 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */, ); path = Profiler; @@ -506,8 +503,6 @@ 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */, 14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */, 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */, - 1385D0351B6661DB000A309B /* RCTModuleMap.h */, - 1385D0331B665AAE000A309B /* RCTModuleMap.m */, 14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */, 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */, 142014181B32094000CC17BA /* RCTPerformanceLogger.h */, @@ -643,6 +638,7 @@ 1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */, 14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */, 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */, + 13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */, 13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */, 14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */, 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */, @@ -683,8 +679,6 @@ 83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */, 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, - 14BF71801C04793D00C97D0C /* RCTProfileTrampoline-i386.S in Sources */, - 1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */, 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, 83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */, 13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */, diff --git a/React/Views/RCTComponentData.h b/React/Views/RCTComponentData.h index 9a43e9865..f7151c22b 100644 --- a/React/Views/RCTComponentData.h +++ b/React/Views/RCTComponentData.h @@ -12,16 +12,19 @@ #import "RCTComponent.h" #import "RCTDefines.h" +@class RCTBridge; @class RCTShadowView; @class RCTViewManager; @class UIView; @interface RCTComponentData : NSObject +@property (nonatomic, readonly) Class managerClass; @property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, strong, readonly) RCTViewManager *manager; -- (instancetype)initWithManager:(RCTViewManager *)manager NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithManagerClass:(Class)managerClass + bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; - (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary *)props; - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag; diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 20c64c3e1..dbcbc10c9 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -44,16 +44,21 @@ typedef void (^RCTPropBlock)(id view, id json); RCTShadowView *_defaultShadowView; NSMutableDictionary *_viewPropBlocks; NSMutableDictionary *_shadowPropBlocks; + RCTBridge *_bridge; } -- (instancetype)initWithManager:(RCTViewManager *)manager +@synthesize manager = _manager; + +- (instancetype)initWithManagerClass:(Class)managerClass + bridge:(RCTBridge *)bridge { if ((self = [super init])) { - _manager = manager; + _bridge = bridge; + _managerClass = managerClass; _viewPropBlocks = [NSMutableDictionary new]; _shadowPropBlocks = [NSMutableDictionary new]; - _name = RCTBridgeModuleNameForClass([manager class]); + _name = RCTBridgeModuleNameForClass(_managerClass); RCTAssert(_name.length, @"Invalid moduleName '%@'", _name); if ([_name hasSuffix:@"Manager"]) { _name = [_name substringToIndex:_name.length - @"Manager".length]; @@ -62,13 +67,21 @@ typedef void (^RCTPropBlock)(id view, id json); return self; } +- (RCTViewManager *)manager +{ + if (!_manager) { + _manager = [_bridge moduleForClass:_managerClass]; + } + return _manager; +} + RCT_NOT_IMPLEMENTED(- (instancetype)init) - (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary *)props { RCTAssertMainThread(); - UIView *view = (UIView *)(props ? [_manager viewWithProps:props] : [_manager view]); + UIView *view = (UIView *)(props ? [self.manager viewWithProps:props] : [_manager view]); view.reactTag = tag; view.multipleTouchEnabled = YES; view.userInteractionEnabled = YES; // required for touch handling @@ -78,7 +91,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag { - RCTShadowView *shadowView = [_manager shadowView]; + RCTShadowView *shadowView = [self.manager shadowView]; shadowView.reactTag = tag; shadowView.viewName = _name; return shadowView; @@ -310,15 +323,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (NSDictionary *)viewConfig { - Class managerClass = [_manager class]; - NSMutableArray *directEvents = [NSMutableArray new]; - if (RCTClassOverridesInstanceMethod(managerClass, @selector(customDirectEventTypes))) { - NSArray *events = [_manager customDirectEventTypes]; + if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customDirectEventTypes))) { + NSArray *events = [self.manager customDirectEventTypes]; if (RCT_DEBUG) { RCTAssert(!events || [events isKindOfClass:[NSArray class]], @"customDirectEventTypes must return an array, but %@ returned %@", - managerClass, [events class]); + _managerClass, [events class]); } for (NSString *event in events) { [directEvents addObject:RCTNormalizeInputEventName(event)]; @@ -326,12 +337,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } NSMutableArray *bubblingEvents = [NSMutableArray new]; - if (RCTClassOverridesInstanceMethod(managerClass, @selector(customBubblingEventTypes))) { - NSArray *events = [_manager customBubblingEventTypes]; + if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) { + NSArray *events = [self.manager customBubblingEventTypes]; if (RCT_DEBUG) { RCTAssert(!events || [events isKindOfClass:[NSArray class]], @"customBubblingEventTypes must return an array, but %@ returned %@", - managerClass, [events class]); + _managerClass, [events class]); } for (NSString *event in events) { [bubblingEvents addObject:RCTNormalizeInputEventName(event)]; @@ -340,7 +351,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) unsigned int count = 0; NSMutableDictionary *propTypes = [NSMutableDictionary new]; - Method *methods = class_copyMethodList(object_getClass(managerClass), &count); + Method *methods = class_copyMethodList(object_getClass(_managerClass), &count); for (unsigned int i = 0; i < count; i++) { Method method = methods[i]; SEL selector = method_getName(method); @@ -349,7 +360,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) NSRange nameRange = [methodName rangeOfString:@"_"]; if (nameRange.length) { NSString *name = [methodName substringFromIndex:nameRange.location + 1]; - NSString *type = ((NSArray *(*)(id, SEL))objc_msgSend)(managerClass, selector)[0]; + NSString *type = ((NSArray *(*)(id, SEL))objc_msgSend)(_managerClass, selector)[0]; if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) { RCTLogError(@"Property '%@' of component '%@' redefined from '%@' " "to '%@'", name, _name, propTypes[name], type); diff --git a/React/Views/RCTModalHostViewController.m b/React/Views/RCTModalHostViewController.m index 055036726..ce40551bd 100644 --- a/React/Views/RCTModalHostViewController.m +++ b/React/Views/RCTModalHostViewController.m @@ -9,7 +9,8 @@ #import "RCTModalHostViewController.h" -@implementation RCTModalHostViewController { +@implementation RCTModalHostViewController +{ CGRect _lastViewFrame; } diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index 3ad022b21..ae3b21f2c 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -20,18 +20,12 @@ RCT_EXPORT_MODULE() -- (instancetype)init -{ - if ((self = [super init])) { - _hostViews = [NSHashTable weakObjectsHashTable]; - } - - return self; -} - - (UIView *)view { UIView *view = [[RCTModalHostView alloc] initWithBridge:self.bridge]; + if (_hostViews) { + _hostViews = [NSHashTable weakObjectsHashTable]; + } [_hostViews addObject:view]; return view; } diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index def09910d..235f52a7b 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -30,7 +30,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary *)constantsToExport -{ - return nil; -} - - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(__unused RCTShadowView *)shadowView { return nil;