Unregistered modules will now only error when called, not on bridge init

Summary:
Occasionally people create RCTBridgeModule subclasses or base classes that are not intended to be accessed from JS, and so they don't export them. This was previously flagged as an error by the system. I've now downgraded this error to a warning at startup, and deferred the redbox error until the module is directly accessed by native or JS code.
This commit is contained in:
Nick Lockwood 2015-07-27 08:51:28 -07:00
parent 2c5290946b
commit 7996c32211
10 changed files with 153 additions and 32 deletions

View File

@ -1034,6 +1034,7 @@
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES;
@ -1090,6 +1091,7 @@
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES;

View File

@ -17,6 +17,7 @@
#import "RCTJavaScriptLoader.h"
#import "RCTLog.h"
#import "RCTModuleData.h"
#import "RCTModuleMap.h"
#import "RCTModuleMethod.h"
#import "RCTPerformanceLogger.h"
#import "RCTPerfStats.h"
@ -62,8 +63,8 @@ id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void)
{
BOOL _loading;
__weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
NSMutableArray *_modules;
NSDictionary *_modulesByName;
NSMutableArray *_moduleDataByID;
RCTModuleMap *_modulesByName;
CADisplayLink *_mainDisplayLink;
CADisplayLink *_jsDisplayLink;
NSMutableSet *_frameUpdateObservers;
@ -89,7 +90,7 @@ id<RCTJavaScriptExecutor> RCTGetLatestExecutor(void)
*/
_valid = YES;
_loading = YES;
_modules = [[NSMutableArray alloc] init];
_moduleDataByID = [[NSMutableArray alloc] init];
_frameUpdateObservers = [[NSMutableSet alloc] init];
_scheduledCalls = [[NSMutableArray alloc] init];
_scheduledCallbacks = [[RCTSparseArray alloc] init];
@ -166,7 +167,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
}
// Instantiate modules
_modules = [[NSMutableArray alloc] init];
_moduleDataByID = [[NSMutableArray alloc] init];
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
for (Class moduleClass in RCTGetModuleClasses()) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
@ -199,7 +200,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
}
// Store modules
_modulesByName = [modulesByName copy];
_modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName];
/**
* The executor is a bridge module, wait for it to be created and set it before
@ -217,9 +218,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
}
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
uid:@(_modules.count)
uid:@(_moduleDataByID.count)
instance:module];
[_modules addObject:moduleData];
[_moduleDataByID addObject:moduleData];
if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
[_frameUpdateObservers addObject:moduleData];
@ -236,7 +237,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
// Inject module data into JS context
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
for (RCTModuleData *moduleData in _modules) {
for (RCTModuleData *moduleData in _moduleDataByID) {
config[moduleData.name] = moduleData.config;
}
NSString *configJSON = RCTJSONStringify(@{
@ -275,7 +276,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
RCTProfileEndEvent(@"JavaScript dowload", @"init,download", @[]);
RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]);
_loading = NO;
if (!self.isValid) {
@ -363,7 +364,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
// Invalidate modules
dispatch_group_t group = dispatch_group_create();
for (RCTModuleData *moduleData in _modules) {
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.instance == _javaScriptExecutor) {
continue;
}
@ -387,7 +388,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
if (RCTProfileIsProfiling()) {
RCTProfileUnhookModules(self);
}
_modules = nil;
_moduleDataByID = nil;
_modulesByName = nil;
_frameUpdateObservers = nil;
@ -568,9 +569,15 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
return;
}
NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:_modules.count];
NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsStrongMemory
capacity:_moduleDataByID.count];
for (NSUInteger i = 0; i < numRequests; i++) {
RCTModuleData *moduleData = _modules[[moduleIDs[i] integerValue]];
RCTModuleData *moduleData = _moduleDataByID[[moduleIDs[i] integerValue]];
if (RCT_DEBUG) {
// verify that class has been registered
(void)_modulesByName[moduleData.name];
}
NSMutableOrderedSet *set = [buckets objectForKey:moduleData];
if (!set) {
set = [[NSMutableOrderedSet alloc] init];
@ -596,12 +603,13 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
params:paramsArrays[index]];
}
}
RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) });
RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) });
}];
}
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
for (RCTModuleData *moduleData in _modules) {
for (RCTModuleData *moduleData in _moduleDataByID) {
if ([moduleData.instance respondsToSelector:@selector(batchDidComplete)]) {
[moduleData dispatchBlock:^{
[moduleData.instance batchDidComplete];
@ -624,10 +632,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL
return NO;
}
RCTProfileBeginEvent();
RCTModuleData *moduleData = _modules[moduleID];
RCTModuleData *moduleData = _moduleDataByID[moduleID];
if (RCT_DEBUG && !moduleData) {
RCTLogError(@"No module found for id '%zd'", moduleID);
return NO;

View File

@ -80,6 +80,14 @@ NSString *RCTBridgeModuleNameForClass(Class cls)
return name;
}
/**
* Check if class has been registered
*/
BOOL RCTBridgeModuleClassIsRegistered(Class);
BOOL RCTBridgeModuleClassIsRegistered(Class cls)
{
return [objc_getAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered) ?: @YES boolValue];
}
@implementation RCTBridge
@ -108,8 +116,12 @@ dispatch_queue_t RCTJSThread;
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![RCTModuleClasses containsObject:cls]) {
RCTLogError(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", NSStringFromClass(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;
}
@ -254,5 +266,4 @@ RCT_NOT_IMPLEMENTED(-init)
RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
method:(__unused NSString *)method
arguments:(__unused NSArray *)args);
@end

View File

@ -17,7 +17,7 @@
@property (nonatomic, strong, readonly) NSNumber *uid;
@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;
@property (nonatomic, strong, readonly) Class cls;
@property (nonatomic, strong, readonly) Class moduleClass;
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, copy, readonly) NSArray *methods;
@property (nonatomic, copy, readonly) NSDictionary *config;
@ -28,7 +28,6 @@
uid:(NSNumber *)uid
instance:(id<RCTBridgeModule>)instance NS_DESIGNATED_INITIALIZER;
- (void)dispatchBlock:(dispatch_block_t)block;
- (void)dispatchBlock:(dispatch_block_t)block dispatchGroup:(dispatch_group_t)group;

View File

@ -23,8 +23,8 @@
_javaScriptExecutor = javaScriptExecutor;
_uid = uid;
_instance = instance;
_cls = [instance class];
_name = RCTBridgeModuleNameForClass(_cls);
_moduleClass = [instance class];
_name = RCTBridgeModuleNameForClass(_moduleClass);
[self loadMethods];
[self generateConfig];
@ -39,18 +39,18 @@ RCT_NOT_IMPLEMENTED(-init);
{
NSMutableArray *moduleMethods = [[NSMutableArray alloc] init];
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(_cls), &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 *entries = ((NSArray *(*)(id, SEL))imp)(_cls, selector);
NSArray *entries = ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithObjCMethodName:entries[1]
JSMethodName:entries[0]
moduleClass:_cls];
moduleClass:_moduleClass];
[moduleMethods addObject:moduleMethod];
}

16
React/Base/RCTModuleMap.h Normal file
View File

@ -0,0 +1,16 @@
/**
* 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 <Foundation/Foundation.h>
@interface RCTModuleMap : NSDictionary
- (instancetype)initWithDictionary:(NSDictionary *)modulesByName NS_DESIGNATED_INITIALIZER;
@end

79
React/Base/RCTModuleMap.m Normal file
View File

@ -0,0 +1,79 @@
/**
* 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(-init)
RCT_NOT_IMPLEMENTED(-initWithCoder:aDecoder)
RCT_NOT_IMPLEMENTED(-initWithObjects:(const id [])objects
forKeys:(const id<NSCopying> [])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<RCTBridgeModule> 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

View File

@ -148,7 +148,7 @@ void RCTProfileHookModules(RCTBridge *bridge)
{
for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
[moduleData dispatchBlock:^{
Class moduleClass = moduleData.cls;
Class moduleClass = moduleData.moduleClass;
Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
if (!proxyClass) {
@ -192,8 +192,8 @@ void RCTProfileUnhookModules(RCTBridge *bridge)
{
for (RCTModuleData *moduleData in [bridge valueForKey:@"_modules"]) {
Class proxyClass = object_getClass(moduleData.instance);
if (moduleData.cls != proxyClass) {
object_setClass(moduleData.instance, moduleData.cls);
if (moduleData.moduleClass != proxyClass) {
object_setClass(moduleData.instance, moduleData.moduleClass);
objc_disposeClassPair(proxyClass);
}
};

View File

@ -169,8 +169,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
}
[_contentView removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge];
_contentView.backgroundColor = self.backgroundColor;
[self insertSubview:_contentView atIndex:0];

View File

@ -23,6 +23,7 @@
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 */; };
138D6A141B53CD290074A87E /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A131B53CD290074A87E /* RCTCache.m */; };
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; };
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; };
@ -125,6 +126,8 @@
137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = "<group>"; };
137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = "<group>"; };
137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = "<group>"; };
1385D0331B665AAE000A309B /* RCTModuleMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMap.m; sourceTree = "<group>"; };
1385D0351B6661DB000A309B /* RCTModuleMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTModuleMap.h; sourceTree = "<group>"; };
138D6A121B53CD290074A87E /* RCTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCache.h; sourceTree = "<group>"; };
138D6A131B53CD290074A87E /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = "<group>"; };
13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = "<group>"; };
@ -437,6 +440,8 @@
83CBBA4E1A601E3B00E9B192 /* RCTLog.m */,
14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */,
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */,
1385D0351B6661DB000A309B /* RCTModuleMap.h */,
1385D0331B665AAE000A309B /* RCTModuleMap.m */,
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */,
142014181B32094000CC17BA /* RCTPerformanceLogger.h */,
@ -612,6 +617,7 @@
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */,
1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */,
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
@ -654,6 +660,7 @@
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
@ -696,6 +703,7 @@
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PREPROCESSOR_DEFINITIONS = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;