react-native/React/Base/RCTBridge.m

326 lines
8.2 KiB
Mathematica
Raw Normal View History

/**
* 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 "RCTBridge.h"
#import <objc/runtime.h>
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
2015-04-02 14:33:21 +00:00
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerformanceLogger.h"
#import "RCTUtils.h"
2015-04-11 22:08:00 +00:00
NSString *const RCTReloadNotification = @"RCTReloadNotification";
NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification";
2015-04-11 22:08:00 +00:00
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules";
2015-04-11 22:08:00 +00:00
@class RCTBatchedBridge;
@interface RCTBatchedBridge : RCTBridge <RCTInvalidating>
@property (nonatomic, weak) RCTBridge *parentBridge;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
@end
@interface RCTBridge ()
@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
@end
static NSMutableArray<Class> *RCTModuleClasses;
NSArray<Class> *RCTGetModuleClasses(void);
NSArray<Class> *RCTGetModuleClasses(void)
{
return RCTModuleClasses;
}
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
*/
void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
});
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
moduleClass);
// Register module
[RCTModuleClasses addObject:moduleClass];
}
/**
* This function returns the module name for a given class.
*/
NSString *RCTBridgeModuleNameForClass(Class cls)
{
#if RCT_DEV
RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], @"Bridge module classes must conform to RCTBridgeModule");
#endif
NSString *name = [cls moduleName];
if (name.length == 0) {
name = NSStringFromClass(cls);
}
if ([name hasPrefix:@"RK"]) {
name = [name stringByReplacingCharactersInRange:(NSRange){0,@"RK".length} withString:@"RCT"];
}
return name;
}
/**
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
* This function checks if a class has been registered
*/
BOOL RCTBridgeModuleClassIsRegistered(Class cls)
{
return [objc_getAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered) ?: @YES boolValue];
}
@implementation RCTBridge
2015-06-19 11:18:54 +00:00
dispatch_queue_t RCTJSThread;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
2015-06-19 11:18:54 +00:00
// Set up JS thread
RCTJSThread = (id)kCFNull;
#if RCT_DEBUG
// Set up module classes
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![RCTModuleClasses containsObject:cls]) {
RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", cls);
RCTRegisterModule(cls);
objc_setAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered,
@NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
break;
}
superclass = class_getSuperclass(superclass);
}
}
free(classes);
2015-06-19 11:18:54 +00:00
#endif
});
}
2015-08-19 12:27:43 +00:00
static RCTBridge *RCTCurrentBridgeInstance = nil;
/**
* The last current active bridge instance. This is set automatically whenever
* the bridge is accessed. It can be useful for static functions or singletons
* that need to access the bridge for purposes such as logging, but should not
* be relied upon to return any particular instance, due to race conditions.
*/
+ (instancetype)currentBridge
{
return RCTCurrentBridgeInstance;
}
+ (void)setCurrentBridge:(RCTBridge *)currentBridge
{
RCTCurrentBridgeInstance = currentBridge;
}
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
launchOptions:(NSDictionary *)launchOptions
{
RCTAssertMainThread();
if ((self = [super init])) {
RCTPerformanceLoggerStart(RCTPLTTI);
_delegate = delegate;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
}
return self;
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
RCTAssertMainThread();
if ((self = [super init])) {
RCTPerformanceLoggerStart(RCTPLTTI);
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)dealloc
{
/**
* This runs only on the main thread, but crashes the subclass
* RCTAssertMainThread();
*/
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self invalidate];
}
- (void)bindKeys
{
RCTAssertMainThread();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
#if TARGET_IPHONE_SIMULATOR
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(__unused UIKeyCommand *command) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
object:nil
userInfo:nil];
}];
#endif
}
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
- (NSArray<Class> *)moduleClasses
{
return _batchedBridge.moduleClasses;
}
- (id)moduleForName:(NSString *)moduleName
{
return [_batchedBridge moduleForName:moduleName];
}
- (id)moduleForClass:(Class)moduleClass
{
return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)];
}
- (RCTEventDispatcher *)eventDispatcher
{
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
return [self moduleForClass:[RCTEventDispatcher class]];
}
- (void)reload
{
/**
* AnyThread
*/
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
[self setUp];
});
}
- (void)setUp
{
RCTAssertMainThread();
_bundleURL = [self.delegate sourceURLForBridge:self] ?: _bundleURL;
// Sanitize the bundle URL
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
_batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
}
- (BOOL)isLoading
{
return _batchedBridge.loading;
}
- (BOOL)isValid
{
return _batchedBridge.valid;
}
- (void)invalidate
{
RCTAssertMainThread();
[_batchedBridge invalidate];
_batchedBridge = nil;
}
2015-08-19 12:27:43 +00:00
- (void)logMessage:(NSString *)message level:(NSString *)level
{
2015-08-19 12:27:43 +00:00
[_batchedBridge logMessage:message level:level];
}
#define RCT_INNER_BRIDGE_ONLY(...) \
- (void)__VA_ARGS__ \
{ \
NSString *errorMessage = [NSString stringWithFormat:@"Called method \"%@\" on top level bridge. \
This method should oly be called from bridge instance in a bridge module", @(__func__)]; \
RCTFatal(RCTErrorWithMessage(errorMessage)); \
}
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
[self.batchedBridge enqueueJSCall:moduleDotMethod args:args];
}
RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
method:(__unused NSString *)method
arguments:(__unused NSArray *)args);
@end
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
@implementation RCTBridge(Deprecated)
- (NSDictionary *)modules
{
return _batchedBridge.modules;
}
@end