[ReactNative] Remove module info from the data section + allow external modules

Summary:
@public

The information we required about the exported methods were previously stored
on the binary's DATA section, which didn't allow to access methods on different
static libraries, or in any dynamic library at all. Instead of fetching information
from all the DATA segments, this diff changes the macro in order to create a
new method, that returns the required information about the original method. The
module itself is registered at load time, and on the bridge initialization all
the auto-generated methods are called to gather the methods' information.

Test Plan:
UIExplorer previously had a dependency on `RCTTest`, because it had a `TestModule`
that had to be on the same library. `RCTTest` is now a dependency of
`UIExplorerIntegrationTests`. So the tests themselves running should test it.
This commit is contained in:
Tadeu Zagallo 2015-06-10 03:43:55 -07:00
parent 4690983c10
commit e9095b2f42
5 changed files with 151 additions and 172 deletions

View File

@ -30,6 +30,7 @@
1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */; }; 1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */; };
1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */; }; 1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */; };
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; }; 14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
14B6DA821B276C5900BF4DD1 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58005BEE1ABA80530062E044 /* libRCTTest.a */; };
14D6D7111B220EB3001FB087 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14D6D7101B220EB3001FB087 /* libOCMock.a */; }; 14D6D7111B220EB3001FB087 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14D6D7101B220EB3001FB087 /* libOCMock.a */; };
14D6D71E1B2222EF001FB087 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; 14D6D71E1B2222EF001FB087 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; };
14D6D71F1B2222EF001FB087 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; }; 14D6D71F1B2222EF001FB087 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; };
@ -44,7 +45,6 @@
14D6D7281B2222EF001FB087 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; }; 14D6D7281B2222EF001FB087 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; }; 14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; };
58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58005BEE1ABA80530062E044 /* libRCTTest.a */; };
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; }; D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -229,7 +229,6 @@
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */, 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */,
14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */, 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */,
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */, 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */,
58005BF21ABA80A60062E044 /* libRCTTest.a in Frameworks */,
134180011AA9153C003F314A /* libRCTText.a in Frameworks */, 134180011AA9153C003F314A /* libRCTText.a in Frameworks */,
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */, D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */,
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */, 139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */,
@ -240,6 +239,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
14B6DA821B276C5900BF4DD1 /* libRCTTest.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -865,7 +865,7 @@
"$(SRCROOT)/../../React/**", "$(SRCROOT)/../../React/**",
); );
INFOPLIST_FILE = "$(SRCROOT)/UIExplorer/Info.plist"; INFOPLIST_FILE = "$(SRCROOT)/UIExplorer/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)";
LIBRARY_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = UIExplorer; PRODUCT_NAME = UIExplorer;
@ -883,7 +883,7 @@
"$(SRCROOT)/../../React/**", "$(SRCROOT)/../../React/**",
); );
INFOPLIST_FILE = "$(SRCROOT)/UIExplorer/Info.plist"; INFOPLIST_FILE = "$(SRCROOT)/UIExplorer/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)";
LIBRARY_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = UIExplorer; PRODUCT_NAME = UIExplorer;
@ -907,6 +907,12 @@
INFOPLIST_FILE = UIExplorerIntegrationTests/Info.plist; INFOPLIST_FILE = UIExplorerIntegrationTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.3; IPHONEOS_DEPLOYMENT_TARGET = 8.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
XCTest,
"-ObjC",
);
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer";
}; };
@ -926,6 +932,12 @@
INFOPLIST_FILE = UIExplorerIntegrationTests/Info.plist; INFOPLIST_FILE = UIExplorerIntegrationTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.3; IPHONEOS_DEPLOYMENT_TARGET = 8.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
XCTest,
"-ObjC",
);
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UIExplorer.app/UIExplorer";
}; };

View File

@ -21,17 +21,17 @@
/** /**
* This notification triggers a reload of all bridges currently running. * This notification triggers a reload of all bridges currently running.
*/ */
extern NSString *const RCTReloadNotification; RCT_EXTERN NSString *const RCTReloadNotification;
/** /**
* This notification fires when the bridge has finished loading. * This notification fires when the bridge has finished loading.
*/ */
extern NSString *const RCTJavaScriptDidLoadNotification; RCT_EXTERN NSString *const RCTJavaScriptDidLoadNotification;
/** /**
* This notification fires when the bridge failed to load. * This notification fires when the bridge failed to load.
*/ */
extern NSString *const RCTJavaScriptDidFailToLoadNotification; RCT_EXTERN NSString *const RCTJavaScriptDidFailToLoadNotification;
/** /**
* This block can be used to instantiate modules that require additional * This block can be used to instantiate modules that require additional
@ -43,6 +43,13 @@ extern NSString *const RCTJavaScriptDidFailToLoadNotification;
*/ */
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void); typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
*
*/
RCT_EXTERN void RCTRegisterModule(Class);
/** /**
* This function returns the module name for a given class. * This function returns the module name for a given class.
*/ */

View File

@ -70,6 +70,30 @@ typedef struct section RCTHeaderSection;
NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification"; NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
NSString *const RCTDequeueNotification = @"RCTDequeueNotification"; NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
static NSDictionary *RCTModuleIDsByName;
static NSArray *RCTModuleNamesByID;
static NSArray *RCTModuleClassesByID;
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleIDsByName = [[NSMutableDictionary alloc] init];
RCTModuleNamesByID = [[NSMutableArray alloc] init];
RCTModuleClassesByID = [[NSMutableArray alloc] init];
});
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
NSStringFromClass(moduleClass));
// Register module
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
((NSMutableDictionary *)RCTModuleIDsByName)[moduleName] = @(RCTModuleNamesByID.count);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
[(NSMutableArray *)RCTModuleClassesByID addObject:moduleClass];
}
/** /**
* This function returns the module name for a given class. * This function returns the module name for a given class.
*/ */
@ -122,93 +146,6 @@ static NSArray *RCTJSMethods(void)
return JSMethods; return JSMethods;
} }
/**
* This function scans all exported modules available at runtime and returns an
* array. As a backup, it also scans all classes that implement the
* RTCBridgeModule protocol to ensure they've been exported. This scanning
* functionality is disabled in release mode to improve startup performance.
*/
static NSDictionary *RCTModuleIDsByName;
static NSArray *RCTModuleNamesByID;
static NSArray *RCTModuleClassesByID;
static NSArray *RCTBridgeModuleClassesByModuleID(void)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleIDsByName = [[NSMutableDictionary alloc] init];
RCTModuleNamesByID = [[NSMutableArray alloc] init];
RCTModuleClassesByID = [[NSMutableArray alloc] init];
Dl_info info;
dladdr(&RCTBridgeModuleClassesByModuleID, &info);
const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase;
unsigned long size;
const uint8_t *sectionData = getsectiondata(mach_header, "__DATA", "RCTExportModule", &size);
if (sectionData) {
for (const uint8_t *addr = sectionData;
addr < sectionData + size;
addr += sizeof(const char **)) {
// Get data entry
NSString *entry = @(*(const char **)addr);
NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}]
componentsSeparatedByString:@" "];
// Parse class name
NSString *moduleClassName = parts[0];
NSRange categoryRange = [moduleClassName rangeOfString:@"("];
if (categoryRange.length) {
moduleClassName = [moduleClassName substringToIndex:categoryRange.location];
}
// Get class
Class cls = NSClassFromString(moduleClassName);
RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
NSStringFromClass(cls));
// Register module
NSString *moduleName = RCTBridgeModuleNameForClass(cls);
((NSMutableDictionary *)RCTModuleIDsByName)[moduleName] = @(RCTModuleNamesByID.count);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
[(NSMutableArray *)RCTModuleClassesByID addObject:cls];
}
}
if (RCT_DEBUG) {
// We may be able to get rid of this check in future, once people
// get used to the new registration system. That would potentially
// allow you to create modules that are not automatically registered
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 (![RCTModuleClassesByID containsObject:cls]) {
RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
}
break;
}
superclass = class_getSuperclass(superclass);
}
}
free(classes);
}
});
return RCTModuleClassesByID;
}
// TODO: Can we just replace RCTMakeError with this function instead? // TODO: Can we just replace RCTMakeError with this function instead?
static NSDictionary *RCTJSErrorFromNSError(NSError *error) static NSDictionary *RCTJSErrorFromNSError(NSError *error)
{ {
@ -278,25 +215,42 @@ static NSDictionary *RCTJSErrorFromNSError(NSError *error)
dispatch_block_t _methodQueue; dispatch_block_t _methodQueue;
} }
- (instancetype)initWithReactMethodName:(NSString *)reactMethodName - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
objCMethodName:(NSString *)objCMethodName JSMethodName:(NSString *)JSMethodName
JSMethodName:(NSString *)JSMethodName moduleClass:(Class)moduleClass
{ {
if ((self = [super init])) { if ((self = [super init])) {
static NSRegularExpression *typeRegex;
static NSRegularExpression *selectorRegex;
if (!typeRegex) {
NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))";
NSString *constPattern = @"(?:const)";
NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern];
NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern];
typeRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
NSArray *parts = [[reactMethodName substringWithRange:(NSRange){2, reactMethodName.length - 3}] componentsSeparatedByString:@" "]; selectorRegex = [[NSRegularExpression alloc] initWithPattern:@"(?<=:).*?(?=[a-zA-Z_]+:|$)" options:0 error:NULL];
// Parse class and method
_moduleClassName = parts[0];
NSRange categoryRange = [_moduleClassName rangeOfString:@"("];
if (categoryRange.length) {
_moduleClassName = [_moduleClassName substringToIndex:categoryRange.location];
} }
NSString *selectorString = [parts[1] substringFromIndex:14]; NSMutableArray *argumentNames = [NSMutableArray array];
_selector = NSSelectorFromString(selectorString); [typeRegex enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
_JSMethodName = JSMethodName ?: ({ NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
NSString *methodName = selectorString; [argumentNames addObject:argumentName];
}];
// Remove the parameters' type and name
objCMethodName = [selectorRegex stringByReplacingMatchesInString:objCMethodName
options:0
range:NSMakeRange(0, objCMethodName.length)
withTemplate:@""];
// Remove any spaces since `selector : (Type)name` is a valid syntax
objCMethodName = [objCMethodName stringByReplacingOccurrencesOfString:@" " withString:@""];
_moduleClass = moduleClass;
_moduleClassName = NSStringFromClass(_moduleClass);
_selector = NSSelectorFromString(objCMethodName);
_JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({
NSString *methodName = NSStringFromSelector(_selector);
NSRange colonRange = [methodName rangeOfString:@":"]; NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.length) { if (colonRange.length) {
methodName = [methodName substringToIndex:colonRange.location]; methodName = [methodName substringToIndex:colonRange.location];
@ -304,31 +258,6 @@ static NSDictionary *RCTJSErrorFromNSError(NSError *error)
methodName; methodName;
}); });
static NSRegularExpression *regExp;
if (!regExp) {
NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))";
NSString *constPattern = @"(?:const)";
NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern];
NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern];
regExp = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
}
NSMutableArray *argumentNames = [NSMutableArray array];
[regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
[argumentNames addObject:argumentName];
}];
// Extract class and method details
_moduleClass = NSClassFromString(_moduleClassName);
if (RCT_DEBUG) {
// Sanity check
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"You are attempting to export the method %@, but %@ does not \
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
}
// Get method signature // Get method signature
_methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector]; _methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
@ -554,44 +483,33 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
static RCTSparseArray *methodsByModuleID; static RCTSparseArray *methodsByModuleID;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[RCTModuleClassesByID count]];
Dl_info info; [RCTModuleClassesByID enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
dladdr(&RCTExportedMethodsByModuleID, &info);
const RCTHeaderValue mach_header = (RCTHeaderValue)info.dli_fbase; methodsByModuleID[moduleID] = [[NSMutableArray alloc] init];
unsigned long size; unsigned int methodCount;
const uint8_t *sectionData = getsectiondata(mach_header, "__DATA", "RCTExport", &size); Method *methods = class_copyMethodList(object_getClass(moduleClass), &methodCount);
if (sectionData == NULL) { for (unsigned int i = 0; i < methodCount; i++) {
return; Method method = methods[i];
} SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
NSArray *entries = ((NSArray *(*)(id, SEL))objc_msgSend)(moduleClass, selector);
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithObjCMethodName:entries[1]
JSMethodName:entries[0]
moduleClass:moduleClass];
NSArray *classes = RCTBridgeModuleClassesByModuleID(); [methodsByModuleID[moduleID] addObject:moduleMethod];
NSMutableDictionary *methodsByModuleClassName = [NSMutableDictionary dictionaryWithCapacity:[classes count]]; }
}
for (const uint8_t *addr = sectionData; free(methods);
addr < sectionData + size;
addr += sizeof(const char **) * 3) {
// Get data entry
const char **entries = (const char **) addr;
// Create method
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
objCMethodName:@(entries[1])
JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil];
// Cache method
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
methodsByModuleClassName[moduleMethod.moduleClassName] =
methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
}
methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]];
[classes enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
methodsByModuleID[moduleID] = methodsByModuleClassName[NSStringFromClass(moduleClass)];
}]; }];
}); });
return methodsByModuleID; return methodsByModuleID;
@ -630,7 +548,7 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
remoteModuleConfigByClassName = [[NSMutableDictionary alloc] init]; remoteModuleConfigByClassName = [[NSMutableDictionary alloc] init];
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) { [RCTModuleClassesByID enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID]; NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count]; NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
@ -768,6 +686,38 @@ static NSDictionary *RCTLocalModulesConfig()
static id<RCTJavaScriptExecutor> _latestJSExecutor; static id<RCTJavaScriptExecutor> _latestJSExecutor;
#if RCT_DEBUG
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
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 (![RCTModuleClassesByID containsObject:cls]) {
RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
}
break;
}
superclass = class_getSuperclass(superclass);
}
}
free(classes);
});
}
#endif
- (instancetype)initWithBundleURL:(NSURL *)bundleURL - (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions launchOptions:(NSDictionary *)launchOptions
@ -1006,7 +956,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
// Instantiate modules // Instantiate modules
_modulesByID = [[RCTSparseArray alloc] init]; _modulesByID = [[RCTSparseArray alloc] init];
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy]; NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) { [RCTModuleClassesByID enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
NSString *moduleName = RCTModuleNamesByID[moduleID]; NSString *moduleName = RCTModuleNamesByID[moduleID];
// Check if module instance has already been registered for this name // Check if module instance has already been registered for this name
id<RCTBridgeModule> module = modulesByName[moduleName]; id<RCTBridgeModule> module = modulesByName[moduleName];

View File

@ -9,6 +9,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "RCTDefines.h"
@class RCTBridge; @class RCTBridge;
/** /**
@ -62,8 +64,9 @@ extern const dispatch_queue_t RCTJSThread;
* match the Objective-C class name. * match the Objective-C class name.
*/ */
#define RCT_EXPORT_MODULE(js_name) \ #define RCT_EXPORT_MODULE(js_name) \
+ (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \ RCT_EXTERN void RCTRegisterModule(Class); \
))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; } + (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule([self class]); }
/** /**
* Wrap the parameter line of your method implementation with this macro to * Wrap the parameter line of your method implementation with this macro to
@ -173,11 +176,10 @@ extern const dispatch_queue_t RCTJSThread;
* Like RCT_EXTERN_REMAP_METHOD, but allows setting a custom JavaScript name. * Like RCT_EXTERN_REMAP_METHOD, but allows setting a custom JavaScript name.
*/ */
#define RCT_EXTERN_REMAP_METHOD(js_name, method) \ #define RCT_EXTERN_REMAP_METHOD(js_name, method) \
- (void)__rct_export__##method { \ + (NSArray *)RCT_CONCAT(__rct_export__, __COUNTER__) { \
__attribute__((used, section("__DATA,RCTExport"))) \ return @[@#js_name, @#method]; \
__attribute__((__aligned__(1))) \ } \
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
}
/** /**
* The queue that will be used to call all exported methods. If omitted, this * The queue that will be used to call all exported methods. If omitted, this

View File

@ -74,3 +74,11 @@
#define RCT_NSASSERT 0 #define RCT_NSASSERT 0
#endif #endif
#endif #endif
/**
* Concat two literals. Supports macro expansions
*
* i.e. RCT_CONCAT(foo, __FILE__)
*/
#define RCT_CONCAT2(A, B) A ## B
#define RCT_CONCAT(A, B) RCT_CONCAT2(A, B)