react-native/React/Base/RCTModuleData.m
Nick Lockwood f7edcda5d7 Deprecated RCTDidCreateNativeModules notification
Summary:
public

Thanks to the new lazy initialization system for modules, `RCTDidCreateNativeModules` no longer does what the name implies.

Previously, `RCTDidCreateNativeModules` was fired after all native modules had been initialized. Now, it simply fires each time the bridge is reloaded. Modules are created on demand when they are needed, so most of the assumptions about when `RCTDidCreateNativeModules` will fire are now incorrect.

This diff deprecates `RCTDidCreateNativeModules`, and adds a new notification, `RCTDidInitializeModuleNotification`, which fires each time a module a new module is instantiated.

If you need to access a module at any time you can just call `-[bridge moduleForClass:]` and the module will be instantiated on demand. If you want to access a module *only* after it has already been instantiated, you can use the `RCTDidInitializeModuleNotification` notification.

Reviewed By: tadeuzagallo

Differential Revision: D2755036

fb-gh-sync-id: 25bab6d5eb6fcd35d43125ac45908035eea01487
2015-12-15 05:43:33 -08:00

218 lines
6.1 KiB
Objective-C

/**
* 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 "RCTModuleData.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTModuleMethod.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@implementation RCTModuleData
{
NSString *_queueName;
__weak RCTBridge *_bridge;
}
@synthesize methods = _methods;
@synthesize instance = _instance;
@synthesize methodQueue = _methodQueue;
- (instancetype)initWithModuleClass:(Class)moduleClass
bridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_moduleClass = moduleClass;
_bridge = bridge;
}
return self;
}
- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
{
if ((self = [super init])) {
_instance = instance;
_moduleClass = [instance class];
[self cacheImplementedSelectors];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init);
- (BOOL)hasInstance
{
return _instance != nil;
}
- (id<RCTBridgeModule>)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];
[self cacheImplementedSelectors];
}
return _instance;
}
- (void)cacheImplementedSelectors
{
_implementsBatchDidComplete = [_instance respondsToSelector:@selector(batchDidComplete)];
_implementsPartialBatchDidFlush = [_instance respondsToSelector:@selector(partialBatchDidFlush)];
}
- (void)setBridgeForInstance:(RCTBridge *)bridge
{
_bridge = 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);
}
}
[bridge registerModuleForFrameUpdates:self];
}
- (NSString *)name
{
return RCTBridgeModuleNameForClass(_moduleClass);
}
- (NSArray<id<RCTBridgeMethod>> *)methods
{
if (!_methods) {
NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];
if ([_instance respondsToSelector:@selector(methodsToExport)]) {
[moduleMethods addObjectsFromArray:[_instance methodsToExport]];
}
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
NSArray<NSString *> *entries =
((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);
id<RCTBridgeMethod> moduleMethod =
[[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
JSMethodName:entries[0]
moduleClass:_moduleClass];
[moduleMethods addObject:moduleMethod];
}
}
free(methods);
_methods = [moduleMethods copy];
}
return _methods;
}
- (NSArray *)config
{
__block NSDictionary<NSString *, id> *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
}
NSMutableArray<NSString *> *methods = self.methods.count ? [NSMutableArray new] : nil;
NSMutableArray<NSNumber *> *asyncMethods = nil;
for (id<RCTBridgeMethod> method in self.methods) {
if (method.functionType == RCTFunctionTypePromise) {
if (!asyncMethods) {
asyncMethods = [NSMutableArray new];
}
[asyncMethods addObject:@(methods.count)];
}
[methods addObject:method.JSMethodName];
}
NSMutableArray *config = [NSMutableArray new];
[config addObject:self.name];
if (constants.count) {
[config addObject:constants];
}
if (methods) {
[config addObject:methods];
if (asyncMethods) {
[config addObject:asyncMethods];
}
}
return config;
}
- (dispatch_queue_t)methodQueue
{
if (!_methodQueue) {
BOOL implementsMethodQueue = [self.instance respondsToSelector:@selector(methodQueue)];
if (implementsMethodQueue) {
_methodQueue = _instance.methodQueue;
}
if (!_methodQueue) {
// Create new queue (store queueName, as it isn't retained by dispatch_queue)
_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:_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.", self.name);
}
}
}
// Needs to be sent after bridge has been set for all module instances.
// Makes sense to put it here, since the same rules apply for methodQueue.
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTDidInitializeModuleNotification
object:_bridge userInfo:@{@"module": _instance}];
}
return _methodQueue;
}
- (void)invalidate
{
_methodQueue = nil;
}
@end