From cb120801799b60c8c241781099caff8f2b76ebd3 Mon Sep 17 00:00:00 2001
From: Pieter De Baets <pieterdb@fb.com>
Date: Mon, 24 Jul 2017 06:46:01 -0700
Subject: [PATCH] Replace exported method registration with statically
 allocated struct

Reviewed By: fromcelticpark

Differential Revision: D5389383

fbshipit-source-id: 9eb29b254b616574966b43ad24aa880d44589652
---
 Libraries/RCTTest/RCTTestRunner.h             | 21 ++--
 .../RNTesterUnitTests/RCTAllocationTests.m    | 22 +++--
 .../RCTMethodArgumentTests.m                  | 30 +++---
 ...eMethodTests.m => RCTModuleMethodTests.mm} | 50 +++++-----
 React/Base/RCTBatchedBridge.mm                |  2 +-
 React/Base/RCTBridgeMethod.h                  |  2 +-
 React/Base/RCTBridgeModule.h                  | 18 +++-
 React/Base/RCTDefines.h                       |  4 +
 React/Base/RCTModuleData.mm                   | 13 +--
 React/Base/RCTModuleMethod.h                  |  5 +-
 React/Base/RCTModuleMethod.m                  | 95 +++++++++----------
 React/CxxModule/RCTCxxMethod.mm               | 29 +++---
 React/CxxModule/RCTNativeModule.mm            |  4 +-
 ReactCommon/cxxreact/CxxModule.h              |  5 +
 ReactCommon/cxxreact/CxxNativeModule.cpp      |  4 +-
 15 files changed, 161 insertions(+), 143 deletions(-)
 rename RNTester/RNTesterUnitTests/{RCTModuleMethodTests.m => RCTModuleMethodTests.mm} (75%)

diff --git a/Libraries/RCTTest/RCTTestRunner.h b/Libraries/RCTTest/RCTTestRunner.h
index d8e909a4981fff..e2def90b2aa931 100644
--- a/Libraries/RCTTest/RCTTestRunner.h
+++ b/Libraries/RCTTest/RCTTestRunner.h
@@ -15,16 +15,17 @@
 #define FB_REFERENCE_IMAGE_DIR ""
 #endif
 
-#define RCT_RUN_RUNLOOP_WHILE(CONDITION) \
-{ \
-  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
-  while ((CONDITION)) { \
-    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
-    if ([timeout timeIntervalSinceNow] <= 0) { \
-      XCTFail(@"Runloop timed out before condition was met"); \
-      break; \
-    } \
-  } \
+#define RCT_RUN_RUNLOOP_WHILE(CONDITION)                                                          \
+{                                                                                                 \
+  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5];                                      \
+  NSRunLoop *runloop = [NSRunLoop mainRunLoop];                                                   \
+  while ((CONDITION)) {                                                                           \
+    [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; \
+    if ([timeout timeIntervalSinceNow] <= 0) {                                                    \
+      XCTFail(@"Runloop timed out before condition was met");                                     \
+      break;                                                                                      \
+    }                                                                                             \
+  }                                                                                               \
 }
 
 /**
diff --git a/RNTester/RNTesterUnitTests/RCTAllocationTests.m b/RNTester/RNTesterUnitTests/RCTAllocationTests.m
index 917ca00c9e9d47..7cf8a7cc1e009b 100644
--- a/RNTester/RNTesterUnitTests/RCTAllocationTests.m
+++ b/RNTester/RNTesterUnitTests/RCTAllocationTests.m
@@ -112,9 +112,7 @@ - (void)testModulesAreInvalidated
   AllocationTestModule *module = [AllocationTestModule new];
   @autoreleasepool {
     RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
-                                              moduleProvider:^{
-                                                return @[module];
-                                              }
+                                              moduleProvider:^{ return @[module]; }
                                                launchOptions:nil];
     XCTAssertTrue(module.isValid, @"AllocationTestModule should be valid");
     (void)bridge;
@@ -130,12 +128,10 @@ - (void)testModulesAreDeallocated
   @autoreleasepool {
     AllocationTestModule *module = [AllocationTestModule new];
     RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
-                                              moduleProvider:^{
-                                                return @[module];
-                                              }
+                                              moduleProvider:^{ return @[module]; }
                                                launchOptions:nil];
+    XCTAssertNotNil(module, @"AllocationTestModule should have been created");
     weakModule = module;
-    XCTAssertNotNil(weakModule, @"AllocationTestModule should have been created");
     (void)bridge;
   }
 
@@ -145,11 +141,18 @@ - (void)testModulesAreDeallocated
 
 - (void)testModuleMethodsAreDeallocated
 {
+  static RCTMethodInfo methodInfo = {
+    .objcName = "test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d",
+    .jsName = "",
+    .isSync = false
+  };
+
   __weak RCTModuleMethod *weakMethod;
   @autoreleasepool {
-    __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" isSync:NO moduleClass:[AllocationTestModule class]];
-    weakMethod = method;
+    __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithExportedMethod:&methodInfo
+                                                                                  moduleClass:[AllocationTestModule class]];
     XCTAssertNotNil(method, @"RCTModuleMethod should have been created");
+    weakMethod = method;
   }
 
   RCT_RUN_RUNLOOP_WHILE(weakMethod)
@@ -172,7 +175,6 @@ - (void)testContentViewIsInvalidated
 #if !TARGET_OS_TV // userInteractionEnabled is true for Apple TV views
   XCTAssertFalse(rootContentView.userInteractionEnabled, @"RCTContentView should have been invalidated");
 #endif
-
 }
 
 - (void)testUnderlyingBridgeIsDeallocated
diff --git a/RNTester/RNTesterUnitTests/RCTMethodArgumentTests.m b/RNTester/RNTesterUnitTests/RCTMethodArgumentTests.m
index ed74c60577b0fd..4f63410344d430 100644
--- a/RNTester/RNTesterUnitTests/RCTMethodArgumentTests.m
+++ b/RNTester/RNTesterUnitTests/RCTMethodArgumentTests.m
@@ -19,12 +19,12 @@ @interface RCTMethodArgumentTests : XCTestCase
 
 @implementation RCTMethodArgumentTests
 
-extern SEL RCTParseMethodSignature(NSString *methodSignature, NSArray **argTypes);
+extern SEL RCTParseMethodSignature(const char *methodSignature, NSArray **argTypes);
 
 - (void)testOneArgument
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSInteger)foo";
+  const char *methodSignature = "foo:(NSInteger)foo";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
   XCTAssertEqual(arguments.count, (NSUInteger)1);
@@ -34,7 +34,7 @@ - (void)testOneArgument
 - (void)testTwoArguments
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSInteger)foo bar:(BOOL)bar";
+  const char *methodSignature = "foo:(NSInteger)foo bar:(BOOL)bar";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
@@ -45,7 +45,7 @@ - (void)testTwoArguments
 - (void)testSpaces
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo : (NSInteger)foo bar : (BOOL) bar";
+  const char *methodSignature = "foo : (NSInteger)foo bar : (BOOL) bar";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
@@ -56,7 +56,7 @@ - (void)testSpaces
 - (void)testNewlines
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
+  const char *methodSignature = "foo : (NSInteger)foo\nbar : (BOOL) bar";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
@@ -67,7 +67,7 @@ - (void)testNewlines
 - (void)testUnnamedArgs
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSInteger)foo:(BOOL)bar";
+  const char *methodSignature = "foo:(NSInteger)foo:(BOOL)bar";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo::");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
@@ -78,7 +78,7 @@ - (void)testUnnamedArgs
 - (void)testUntypedUnnamedArgs
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:foo:bar:bar";
+  const char *methodSignature = "foo:foo:bar:bar";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:::");
   XCTAssertEqual(arguments.count, (NSUInteger)3);
@@ -90,7 +90,7 @@ - (void)testUntypedUnnamedArgs
 - (void)testAttributes
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(__attribute__((unused)) NSString *)foo bar:(__unused BOOL)bar";
+  const char *methodSignature = "foo:(__attribute__((unused)) NSString *)foo bar:(__unused BOOL)bar";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
@@ -101,7 +101,7 @@ - (void)testAttributes
 - (void)testNullability
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
+  const char *methodSignature = "foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:baz:");
   XCTAssertEqual(arguments.count, (NSUInteger)3);
@@ -116,7 +116,7 @@ - (void)testNullability
 - (void)testSemicolonStripping
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSString *)foo bar:(BOOL)bar;";
+  const char *methodSignature = "foo:(NSString *)foo bar:(BOOL)bar;";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
@@ -127,7 +127,7 @@ - (void)testSemicolonStripping
 - (void)testUnused
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar";
+  const char *methodSignature = "foo:(__unused NSString *)foo bar:(NSNumber *)bar";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
@@ -140,7 +140,7 @@ - (void)testUnused
 - (void)testGenericArray
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSArray<NSString *> *)foo;";
+  const char *methodSignature = "foo:(NSArray<NSString *> *)foo;";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
   XCTAssertEqual(arguments.count, (NSUInteger)1);
@@ -150,7 +150,7 @@ - (void)testGenericArray
 - (void)testNestedGenericArray
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSArray<NSArray<NSString *> *> *)foo;";
+  const char *methodSignature = "foo:(NSArray<NSArray<NSString *> *> *)foo;";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
   XCTAssertEqual(arguments.count, (NSUInteger)1);
@@ -160,7 +160,7 @@ - (void)testNestedGenericArray
 - (void)testGenericSet
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSSet<NSNumber *> *)foo;";
+  const char *methodSignature = "foo:(NSSet<NSNumber *> *)foo;";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
   XCTAssertEqual(arguments.count, (NSUInteger)1);
@@ -170,7 +170,7 @@ - (void)testGenericSet
 - (void)testGenericDictionary
 {
   NSArray *arguments;
-  NSString *methodSignature = @"foo:(NSDictionary<NSString *, NSNumber *> *)foo;";
+  const char *methodSignature = "foo:(NSDictionary<NSString *, NSNumber *> *)foo;";
   SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
   XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
   XCTAssertEqual(arguments.count, (NSUInteger)1);
diff --git a/RNTester/RNTesterUnitTests/RCTModuleMethodTests.m b/RNTester/RNTesterUnitTests/RCTModuleMethodTests.mm
similarity index 75%
rename from RNTester/RNTesterUnitTests/RCTModuleMethodTests.m
rename to RNTester/RNTesterUnitTests/RCTModuleMethodTests.mm
index b1ac8686361fc9..ff0f09afb4443e 100644
--- a/RNTester/RNTesterUnitTests/RCTModuleMethodTests.m
+++ b/RNTester/RNTesterUnitTests/RCTModuleMethodTests.mm
@@ -37,18 +37,18 @@ @implementation RCTModuleMethodTests
   CGRect _s;
 }
 
-static RCTModuleMethod *buildDefaultMethodWithMethodSignature(NSString *methodSignature) {
-  return [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
-                                             JSMethodName:nil
-                                                   isSync:NO
-                                              moduleClass:[RCTModuleMethodTests class]];
+static RCTModuleMethod *buildDefaultMethodWithMethodSignature(const char *methodSignature)
+{
+  // This leaks a RCTMethodInfo, but it's a test, so...
+  RCTMethodInfo *methodInfo = new RCTMethodInfo {.objcName = methodSignature, .isSync = NO};
+  return [[RCTModuleMethod alloc] initWithExportedMethod:methodInfo moduleClass:[RCTModuleMethodTests class]];
 }
 
-static RCTModuleMethod *buildSyncMethodWithMethodSignature(NSString *methodSignature) {
-  return [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
-                                             JSMethodName:nil
-                                                   isSync:YES
-                                              moduleClass:[RCTModuleMethodTests class]];
+static RCTModuleMethod *buildSyncMethodWithMethodSignature(const char *methodSignature)
+{
+  // This leaks a RCTMethodInfo, but it's a test, so...
+  RCTMethodInfo *methodInfo = new RCTMethodInfo {.objcName = methodSignature, .isSync = YES};
+  return [[RCTModuleMethod alloc] initWithExportedMethod:methodInfo moduleClass:[RCTModuleMethodTests class]];
 }
 
 + (NSString *)moduleName { return nil; }
@@ -62,7 +62,7 @@ - (id)methodThatReturnsNil { return nil; }
 
 - (void)testNonnull
 {
-  NSString *methodSignature = @"doFooWithBar:(nonnull NSString *)bar";
+  const char *methodSignature = "doFooWithBar:(nonnull NSString *)bar";
   RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
   XCTAssertFalse(RCTLogsError(^{
     [method invokeWithBridge:nil module:self arguments:@[@"Hello World"]];
@@ -85,7 +85,7 @@ - (void)testNumbersNonnull
   {
     // Specifying an NSNumber param without nonnull isn't allowed
     XCTAssertTrue(RCTLogsError(^{
-      NSString *methodSignature = @"doFooWithNumber:(NSNumber *)n";
+      const char *methodSignature = "doFooWithNumber:(NSNumber *)n";
       RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
       // Invoke method to trigger parsing
       [method invokeWithBridge:nil module:self arguments:@[@1]];
@@ -93,7 +93,7 @@ - (void)testNumbersNonnull
   }
 
   {
-    NSString *methodSignature = @"doFooWithNumber:(nonnull NSNumber *)n";
+    const char *methodSignature = "doFooWithNumber:(nonnull NSNumber *)n";
     RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
     XCTAssertTrue(RCTLogsError(^{
       [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
@@ -101,7 +101,7 @@ - (void)testNumbersNonnull
   }
 
   {
-    NSString *methodSignature = @"doFooWithDouble:(double)n";
+    const char *methodSignature = "doFooWithDouble:(double)n";
     RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
     XCTAssertTrue(RCTLogsError(^{
       [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
@@ -109,7 +109,7 @@ - (void)testNumbersNonnull
   }
 
   {
-    NSString *methodSignature = @"doFooWithInteger:(NSInteger)n";
+    const char *methodSignature = "doFooWithInteger:(NSInteger)n";
     RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
     XCTAssertTrue(RCTLogsError(^{
       [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
@@ -119,7 +119,7 @@ - (void)testNumbersNonnull
 
 - (void)testStructArgument
 {
-  NSString *methodSignature = @"doFooWithCGRect:(CGRect)s";
+  const char *methodSignature = "doFooWithCGRect:(CGRect)s";
   RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
 
   CGRect r = CGRectMake(10, 20, 30, 40);
@@ -129,7 +129,7 @@ - (void)testStructArgument
 
 - (void)testWhitespaceTolerance
 {
-  NSString *methodSignature = @"doFoo : \t (NSString *)foo";
+  const char *methodSignature = "doFoo : \t (NSString *)foo";
 
   __block RCTModuleMethod *method;
   XCTAssertFalse(RCTLogsError(^{
@@ -146,19 +146,19 @@ - (void)testWhitespaceTolerance
 - (void)testFunctionType
 {
   {
-    NSString *methodSignature = @"doFoo";
+    const char *methodSignature = "doFoo";
     RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
     XCTAssertTrue(method.functionType == RCTFunctionTypeNormal);
   }
 
   {
-    NSString *methodSignature = @"openURL:(NSURL *)URL resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject";
+    const char *methodSignature = "openURL:(NSURL *)URL resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject";
     RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
     XCTAssertTrue(method.functionType == RCTFunctionTypePromise);
   }
 
   {
-    NSString *methodSignature = @"echoString:(NSString *)input";
+    const char *methodSignature = "echoString:(NSString *)input";
     RCTModuleMethod *method = buildSyncMethodWithMethodSignature(methodSignature);
     XCTAssertTrue(method.functionType == RCTFunctionTypeSync);
   }
@@ -167,14 +167,14 @@ - (void)testFunctionType
 - (void)testReturnsValueForSyncFunction
 {
   {
-    NSString *methodSignature = @"echoString:(NSString *)input";
+    const char *methodSignature = "echoString:(NSString *)input";
     RCTModuleMethod *method = buildSyncMethodWithMethodSignature(methodSignature);
     id result = [method invokeWithBridge:nil module:self arguments:@[@"Test String Value"]];
     XCTAssertEqualObjects(result, @"Test String Value");
   }
 
   {
-    NSString *methodSignature = @"methodThatReturnsNil";
+    const char *methodSignature = "methodThatReturnsNil";
     RCTModuleMethod *method = buildSyncMethodWithMethodSignature(methodSignature);
     id result = [method invokeWithBridge:nil module:self arguments:@[]];
     XCTAssertNil(result);
@@ -183,7 +183,7 @@ - (void)testReturnsValueForSyncFunction
 
 - (void)testReturnsNilForDefaultFunction
 {
-  NSString *methodSignature = @"doFoo";
+  const char *methodSignature = "doFoo";
   RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature);
   id result = [method invokeWithBridge:nil module:self arguments:@[]];
   XCTAssertNil(result);
@@ -192,7 +192,7 @@ - (void)testReturnsNilForDefaultFunction
 - (void)testReturnTypeForSyncFunction
 {
   {
-    NSString *methodSignature = @"methodThatReturnsNil";
+    const char *methodSignature = "methodThatReturnsNil";
     RCTModuleMethod *method = buildSyncMethodWithMethodSignature(methodSignature);
     XCTAssertFalse(RCTLogsError(^{
       // Invoke method to trigger parsing
@@ -201,7 +201,7 @@ - (void)testReturnTypeForSyncFunction
   }
 
   {
-    NSString *methodSignature = @"doFoo";
+    const char *methodSignature = "doFoo";
     RCTModuleMethod *method = buildSyncMethodWithMethodSignature(methodSignature);
     XCTAssertTrue(RCTLogsError(^{
       // Invoke method to trigger parsing
diff --git a/React/Base/RCTBatchedBridge.mm b/React/Base/RCTBatchedBridge.mm
index 782eda5e32fe5f..326546f20ea360 100644
--- a/React/Base/RCTBatchedBridge.mm
+++ b/React/Base/RCTBatchedBridge.mm
@@ -1067,7 +1067,7 @@ - (id)callNativeModule:(NSUInteger)moduleID
     }
 
     NSString *message = [NSString stringWithFormat:
-                         @"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
+                         @"Exception '%@' was thrown while invoking %s on target %@ with params %@",
                          exception, method.JSMethodName, moduleData.name, params];
     RCTFatal(RCTErrorWithMessage(message));
     return nil;
diff --git a/React/Base/RCTBridgeMethod.h b/React/Base/RCTBridgeMethod.h
index 37ca1bb4368bc8..8ffea53965c9e2 100644
--- a/React/Base/RCTBridgeMethod.h
+++ b/React/Base/RCTBridgeMethod.h
@@ -30,7 +30,7 @@ static inline const char *RCTFunctionDescriptorFromType(RCTFunctionType type) {
 
 @protocol RCTBridgeMethod <NSObject>
 
-@property (nonatomic, copy, readonly) NSString *JSMethodName;
+@property (nonatomic, readonly) const char *JSMethodName;
 @property (nonatomic, readonly) RCTFunctionType functionType;
 
 - (id)invokeWithBridge:(RCTBridge *)bridge
diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h
index 20febfb1f05e3b..3e31621d0ce9a7 100644
--- a/React/Base/RCTBridgeModule.h
+++ b/React/Base/RCTBridgeModule.h
@@ -47,7 +47,17 @@ typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError
  *
  * NOTE: RCTJSThread is not a real libdispatch queue
  */
-extern dispatch_queue_t RCTJSThread;
+RCT_EXTERN dispatch_queue_t RCTJSThread;
+
+RCT_EXTERN_C_BEGIN
+
+typedef struct RCTMethodInfo {
+  const char *const jsName;
+  const char *const objcName;
+  const BOOL isSync;
+} RCTMethodInfo;
+
+RCT_EXTERN_C_END
 
 /**
  * Provides the interface needed to register a bridge module.
@@ -248,9 +258,9 @@ RCT_EXTERN void RCTRegisterModule(Class); \
  * and also whether this method is synchronous.
  */
 #define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
-  + (NSArray *)RCT_CONCAT(__rct_export__, \
-    RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
-    return @[@#js_name, @#method, @is_blocking_synchronous_method]; \
+  + (const RCTMethodInfo *)RCT_CONCAT(__rct_export__, RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
+    static RCTMethodInfo config = {#js_name, #method, is_blocking_synchronous_method}; \
+    return &config; \
   }
 
 /**
diff --git a/React/Base/RCTDefines.h b/React/Base/RCTDefines.h
index ffb7591f89a7d9..b2681e38c615be 100644
--- a/React/Base/RCTDefines.h
+++ b/React/Base/RCTDefines.h
@@ -16,8 +16,12 @@
  */
 #if defined(__cplusplus)
 #define RCT_EXTERN extern "C" __attribute__((visibility("default")))
+#define RCT_EXTERN_C_BEGIN extern "C" {
+#define RCT_EXTERN_C_END }
 #else
 #define RCT_EXTERN extern __attribute__((visibility("default")))
+#define RCT_EXTERN_C_BEGIN
+#define RCT_EXTERN_C_END
 #endif
 
 /**
diff --git a/React/Base/RCTModuleData.mm b/React/Base/RCTModuleData.mm
index 3c850148f0036c..ae728a2c552503 100644
--- a/React/Base/RCTModuleData.mm
+++ b/React/Base/RCTModuleData.mm
@@ -270,14 +270,9 @@ - (NSString *)name
         SEL selector = method_getName(method);
         if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
           IMP imp = method_getImplementation(method);
-          NSArray *entries =
-            ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);
-          id<RCTBridgeMethod> moduleMethod =
-            [[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
-                                                JSMethodName:entries[0]
-                                                      isSync:((NSNumber *)entries[2]).boolValue
-                                                 moduleClass:_moduleClass];
-
+          auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_moduleClass, selector);
+          id<RCTBridgeMethod> moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod
+                                                                                 moduleClass:_moduleClass];
           [moduleMethods addObject:moduleMethod];
         }
       }
@@ -345,7 +340,7 @@ - (NSArray *)config
       }
       [syncMethods addObject:@(methods.count)];
     }
-    [methods addObject:method.JSMethodName];
+    [methods addObject:@(method.JSMethodName)];
   }
 
   NSArray *config = @[
diff --git a/React/Base/RCTModuleMethod.h b/React/Base/RCTModuleMethod.h
index 3f2800b23cc97f..b9ef3f9ffa7485 100644
--- a/React/Base/RCTModuleMethod.h
+++ b/React/Base/RCTModuleMethod.h
@@ -10,6 +10,7 @@
 #import <Foundation/Foundation.h>
 
 #import <React/RCTBridgeMethod.h>
+#import <React/RCTBridgeModule.h>
 #import <React/RCTNullability.h>
 
 @class RCTBridge;
@@ -27,9 +28,7 @@
 @property (nonatomic, readonly) Class moduleClass;
 @property (nonatomic, readonly) SEL selector;
 
-- (instancetype)initWithMethodSignature:(NSString *)objCMethodName
-                          JSMethodName:(NSString *)JSMethodName
-                                 isSync:(BOOL)isSync
+- (instancetype)initWithExportedMethod:(const RCTMethodInfo *)exportMethod
                            moduleClass:(Class)moduleClass NS_DESIGNATED_INITIALIZER;
 
 @end
diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m
index 68f0344b1787af..df1f02fade5df9 100644
--- a/React/Base/RCTModuleMethod.m
+++ b/React/Base/RCTModuleMethod.m
@@ -41,21 +41,20 @@ - (instancetype)initWithType:(NSString *)type
 @implementation RCTModuleMethod
 {
   Class _moduleClass;
+  const RCTMethodInfo *_methodInfo;
+  NSString *_JSMethodName;
+
+  SEL _selector;
   NSInvocation *_invocation;
   NSArray<RCTArgumentBlock> *_argumentBlocks;
-  NSString *_methodSignature;
-  SEL _selector;
-  BOOL _isSync;
 }
 
-@synthesize JSMethodName = _JSMethodName;
-
 static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index,
                                 id valueOrType, const char *issue)
 {
-  RCTLogError(@"Argument %tu (%@) of %@.%@ %s", index, valueOrType,
+  RCTLogError(@"Argument %tu (%@) of %@.%s %s", index, valueOrType,
               RCTBridgeModuleNameForClass(method->_moduleClass),
-              method->_JSMethodName, issue);
+              method.JSMethodName, issue);
 }
 
 RCT_NOT_IMPLEMENTED(- (instancetype)init)
@@ -114,10 +113,9 @@ static BOOL RCTCheckCallbackMultipleInvocations(BOOL *didInvoke) {
   }
 }
 
-SEL RCTParseMethodSignature(NSString *, NSArray<RCTMethodArgument *> **);
-SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument *> **arguments)
+SEL RCTParseMethodSignature(const char *, NSArray<RCTMethodArgument *> **);
+SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **arguments)
 {
-  const char *input = methodSignature.UTF8String;
   RCTSkipWhitespace(&input);
 
   NSMutableArray *args;
@@ -164,30 +162,25 @@ SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument
   return NSSelectorFromString(selector);
 }
 
-- (instancetype)initWithMethodSignature:(NSString *)methodSignature
-                           JSMethodName:(NSString *)JSMethodName
-                                 isSync:(BOOL)isSync
-                            moduleClass:(Class)moduleClass
+- (instancetype)initWithExportedMethod:(const RCTMethodInfo *)exportedMethod
+                           moduleClass:(Class)moduleClass
 {
   if (self = [super init]) {
     _moduleClass = moduleClass;
-    _methodSignature = [methodSignature copy];
-    _JSMethodName = [JSMethodName copy];
-    _isSync = isSync;
+    _methodInfo = exportedMethod;
   }
-
   return self;
 }
 
 - (void)processMethodSignature
 {
   NSArray<RCTMethodArgument *> *arguments;
-  _selector = RCTParseMethodSignature(_methodSignature, &arguments);
-  RCTAssert(_selector, @"%@ is not a valid selector", _methodSignature);
+  _selector = RCTParseMethodSignature(_methodInfo->objcName, &arguments);
+  RCTAssert(_selector, @"%s is not a valid selector", _methodInfo->objcName);
 
   // Create method invocation
   NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
-  RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", _methodSignature);
+  RCTAssert(methodSignature, @"%s is not a recognized Objective-C method.", sel_getName(_selector));
   NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
   invocation.selector = _selector;
   _invocation = invocation;
@@ -328,8 +321,8 @@ - (void)processMethodSignature
       )
     } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
       RCTAssert(i == numberOfArguments - 2,
-                @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
-                _moduleClass, _methodSignature);
+                @"The RCTPromiseResolveBlock must be the second to last parameter in %@",
+                [self methodName]);
       RCT_ARG_BLOCK(
         if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
           RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function");
@@ -345,8 +338,8 @@ - (void)processMethodSignature
       )
     } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
       RCTAssert(i == numberOfArguments - 1,
-                @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
-                _moduleClass, _methodSignature);
+                @"The RCTPromiseRejectBlock must be the last parameter in %@",
+                [self methodName]);
       RCT_ARG_BLOCK(
         if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
           RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function");
@@ -422,9 +415,9 @@ - (void)processMethodSignature
 
   if (RCT_DEBUG) {
     const char *objcType = _invocation.methodSignature.methodReturnType;
-    if (_isSync && objcType[0] != _C_ID)
-      RCTLogError(@"Return type of %@.%@ should be (id) as the method is \"sync\"",
-                  RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
+    if (_methodInfo->isSync && objcType[0] != _C_ID)
+      RCTLogError(@"Return type of %@.%s should be (id) as the method is \"sync\"",
+                  RCTBridgeModuleNameForClass(_moduleClass), self.JSMethodName);
   }
 
   _argumentBlocks = [argumentBlocks copy];
@@ -434,36 +427,41 @@ - (SEL)selector
 {
   if (_selector == NULL) {
     RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"", (@{ @"module": NSStringFromClass(_moduleClass),
-                                                          @"method": _methodSignature }));
+                                                          @"method": @(_methodInfo->objcName) }));
     [self processMethodSignature];
     RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
   }
   return _selector;
 }
 
-- (NSString *)JSMethodName
+- (const char *)JSMethodName
 {
   NSString *methodName = _JSMethodName;
-  if (methodName.length == 0) {
-    methodName = _methodSignature;
-    NSRange colonRange = [methodName rangeOfString:@":"];
-    if (colonRange.location != NSNotFound) {
-      methodName = [methodName substringToIndex:colonRange.location];
+  if (!methodName) {
+    const char *jsName = _methodInfo->jsName;
+    if (jsName && strlen(jsName) > 0) {
+      methodName = @(jsName);
+    } else {
+      methodName = @(_methodInfo->objcName);
+      NSRange colonRange = [methodName rangeOfString:@":"];
+      if (colonRange.location != NSNotFound) {
+        methodName = [methodName substringToIndex:colonRange.location];
+      }
+      methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+      RCTAssert(methodName.length, @"%s is not a valid JS function name, please"
+                " supply an alternative using RCT_REMAP_METHOD()", _methodInfo->objcName);
     }
-    methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
-    RCTAssert(methodName.length, @"%@ is not a valid JS function name, please"
-              " supply an alternative using RCT_REMAP_METHOD()", _methodSignature);
+    _JSMethodName = methodName;
   }
-  return methodName;
+  return methodName.UTF8String;
 }
 
 - (RCTFunctionType)functionType
 {
-  if ([_methodSignature rangeOfString:@"RCTPromise"].length) {
-    RCTAssert(!_isSync, @"Promises cannot be used in sync functions");
-
+  if (strstr(_methodInfo->objcName, "RCTPromise") != NULL) {
+    RCTAssert(!_methodInfo->isSync, @"Promises cannot be used in sync functions");
     return RCTFunctionTypePromise;
-  } else if (_isSync) {
+  } else if (_methodInfo->isSync) {
     return RCTFunctionTypeSync;
   } else {
     return RCTFunctionTypeNormal;
@@ -494,11 +492,11 @@ - (id)invokeWithBridge:(RCTBridge *)bridge
         expectedCount -= 2;
       }
 
-      RCTLogError(@"%@.%@ was called with %zd arguments but expects %zd arguments. "
+      RCTLogError(@"%@.%s was called with %zd arguments but expects %zd arguments. "
                   @"If you haven\'t changed this method yourself, this usually means that "
                   @"your versions of the native code and JavaScript code are out of sync. "
                   @"Updating both should make this error go away.",
-                  RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
+                  RCTBridgeModuleNameForClass(_moduleClass), self.JSMethodName,
                   actualCount, expectedCount);
       return nil;
     }
@@ -540,7 +538,7 @@ - (id)invokeWithBridge:(RCTBridge *)bridge
   }
 
   id result = nil;
-  if (_isSync) {
+  if (_methodInfo->isSync) {
     void *pointer;
     [_invocation getReturnValue:&pointer];
     result = (__bridge id)pointer;
@@ -554,13 +552,12 @@ - (NSString *)methodName
   if (_selector == NULL) {
     [self processMethodSignature];
   }
-  return [NSString stringWithFormat:@"-[%@ %@]", _moduleClass,
-          NSStringFromSelector(_selector)];
+  return [NSString stringWithFormat:@"-[%@ %s]", _moduleClass, sel_getName(_selector)];
 }
 
 - (NSString *)description
 {
-  return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@(); type: %s>",
+  return [NSString stringWithFormat:@"<%@: %p; exports %@ as %s(); type: %s>",
           [self class], self, [self methodName], self.JSMethodName, RCTFunctionDescriptorFromType(self.functionType)];
 }
 
diff --git a/React/CxxModule/RCTCxxMethod.mm b/React/CxxModule/RCTCxxMethod.mm
index 65a5433a45b004..2c5a9f26538168 100644
--- a/React/CxxModule/RCTCxxMethod.mm
+++ b/React/CxxModule/RCTCxxMethod.mm
@@ -26,17 +26,31 @@ @implementation RCTCxxMethod
   std::unique_ptr<CxxModule::Method> _method;
 }
 
-@synthesize JSMethodName = _JSMethodName;
-
 - (instancetype)initWithCxxMethod:(const CxxModule::Method &)method
 {
   if ((self = [super init])) {
-    _JSMethodName = @(method.name.c_str());
     _method = folly::make_unique<CxxModule::Method>(method);
   }
   return self;
 }
 
+- (const char *)JSMethodName
+{
+  return _method->name.c_str();
+}
+
+- (RCTFunctionType)functionType
+{
+  std::string type(_method->getType());
+  if (type == "sync") {
+    return RCTFunctionTypeSync;
+  } else if (type == "async") {
+    return RCTFunctionTypeNormal;
+  } else {
+    return RCTFunctionTypePromise;
+  }
+}
+
 - (id)invokeWithBridge:(RCTBridge *)bridge
                 module:(id)module
              arguments:(NSArray *)arguments
@@ -110,16 +124,9 @@ - (id)invokeWithBridge:(RCTBridge *)bridge
   }
 }
 
-- (RCTFunctionType)functionType
-{
-  // TODO: support promise-style APIs
-  return _method->syncFunc ? RCTFunctionTypeSync : RCTFunctionTypeNormal;
-}
-
 - (NSString *)description
 {
-  return [NSString stringWithFormat:@"<%@: %p; name = %@>",
-          [self class], self, self.JSMethodName];
+  return [NSString stringWithFormat:@"<%@: %p; name = %s>", [self class], self, self.JSMethodName];
 }
 
 @end
diff --git a/React/CxxModule/RCTNativeModule.mm b/React/CxxModule/RCTNativeModule.mm
index 6d47b1ea6104fc..27b7a50f2b00e2 100644
--- a/React/CxxModule/RCTNativeModule.mm
+++ b/React/CxxModule/RCTNativeModule.mm
@@ -38,7 +38,7 @@
 
   for (id<RCTBridgeMethod> method in m_moduleData.methods) {
     descs.emplace_back(
-      method.JSMethodName.UTF8String,
+      method.JSMethodName,
       RCTFunctionDescriptorFromType(method.functionType)
     );
   }
@@ -103,7 +103,7 @@
     }
 
     NSString *message = [NSString stringWithFormat:
-                         @"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
+                         @"Exception '%@' was thrown while invoking %s on target %@ with params %@",
                          exception, method.JSMethodName, m_moduleData.name, objcParams];
     RCTFatal(RCTErrorWithMessage(message));
   }
diff --git a/ReactCommon/cxxreact/CxxModule.h b/ReactCommon/cxxreact/CxxModule.h
index c67f95d8210d31..cf3b95f8b073f0 100644
--- a/ReactCommon/cxxreact/CxxModule.h
+++ b/ReactCommon/cxxreact/CxxModule.h
@@ -68,6 +68,11 @@ class CxxModule {
 
     std::function<folly::dynamic(folly::dynamic)> syncFunc;
 
+    const char *getType() {
+      assert(func || syncFunc);
+      return func ? (callbacks == 2 ? "promise" : "async") : "sync";
+    }
+
     // std::function/lambda ctors
 
     Method(std::string aname,
diff --git a/ReactCommon/cxxreact/CxxNativeModule.cpp b/ReactCommon/cxxreact/CxxNativeModule.cpp
index c13b53b80273f7..bd4fe385cea20e 100644
--- a/ReactCommon/cxxreact/CxxNativeModule.cpp
+++ b/ReactCommon/cxxreact/CxxNativeModule.cpp
@@ -57,9 +57,7 @@ std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
 
   std::vector<MethodDescriptor> descs;
   for (auto& method : methods_) {
-    assert(method.func || method.syncFunc);
-    auto methodType = method.func ? (method.callbacks == 2 ? "promise" : "async") : "sync";
-    descs.emplace_back(method.name, methodType);
+    descs.emplace_back(method.name, method.getType());
   }
   return descs;
 }