Skip to content

Commit 148a87a

Browse files
committed
[Bridge] RCT_REMAP_METHOD(js_name, selector)
Summary: cc @a2 @nicklockwood This diff introduces a new macro called `RCT_EXPORT_NAMED_METHOD`, which is like `RCT_EXPORT_METHOD` but lets you choose the name of the method in JS. This diff is backwards compatible with the `RCT_EXPORT_METHOD` and legacy `RCT_EXPORT` macros. The entries in the data segment now contain `__func__`, the Obj-C selector signature, and the JS name. If the JS name is `NULL`, we take the legacy `RCT_EXPORT` code path. If the JS name is an empty string, we use the Obj-C selector's name up to the first colon (that is, the behavior of `RCT_EXPORT_METHOD`). Since there are three values in each data segment entry, the macros now specify 1-byte alignment. Without the byte alignment, the compiler defaults to 2-byte alignment meaning that each entry takes up 4 bytes instead of 3. The extra byte isn't a concern but being explicit about the alignment should reduce compiler surprises. Closes facebook#802 Github Author: James Ide <[email protected]> Test Plan: Imported from GitHub, without a `Test Plan:` line.
1 parent 4ca6b4d commit 148a87a

File tree

4 files changed

+122
-102
lines changed

4 files changed

+122
-102
lines changed

React/Base/RCTBridge.m

Lines changed: 86 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -219,20 +219,22 @@ @implementation RCTModuleMethod
219219

220220
static Class _globalExecutorClass;
221221

222-
NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
222+
static NSString *RCTStringUpToFirstArgument(NSString *methodName)
223+
{
223224
NSRange colonRange = [methodName rangeOfString:@":"];
224225
if (colonRange.length) {
225226
methodName = [methodName substringToIndex:colonRange.location];
226227
}
227228
return methodName;
228229
}
229230

230-
- (instancetype)initWithMethodName:(NSString *)methodName
231-
JSMethodName:(NSString *)JSMethodName
231+
- (instancetype)initWithReactMethodName:(NSString *)reactMethodName
232+
objCMethodName:(NSString *)objCMethodName
233+
JSMethodName:(NSString *)JSMethodName
232234
{
233235
if ((self = [super init])) {
234-
_methodName = methodName;
235-
NSArray *parts = [[methodName substringWithRange:(NSRange){2, methodName.length - 3}] componentsSeparatedByString:@" "];
236+
_methodName = reactMethodName;
237+
NSArray *parts = [[reactMethodName substringWithRange:(NSRange){2, reactMethodName.length - 3}] componentsSeparatedByString:@" "];
236238

237239
// Parse class and method
238240
_moduleClassName = parts[0];
@@ -246,7 +248,7 @@ - (instancetype)initWithMethodName:(NSString *)methodName
246248
// New format
247249
NSString *selectorString = [parts[1] substringFromIndex:14];
248250
_selector = NSSelectorFromString(selectorString);
249-
_JSMethodName = RCTStringUpToFirstArgument(selectorString);
251+
_JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString);
250252

251253
static NSRegularExpression *regExp;
252254
if (!regExp) {
@@ -258,8 +260,8 @@ - (instancetype)initWithMethodName:(NSString *)methodName
258260
}
259261

260262
argumentNames = [NSMutableArray array];
261-
[regExp enumerateMatchesInString:JSMethodName options:0 range:NSMakeRange(0, JSMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
262-
NSString *argumentName = [JSMethodName substringWithRange:[result rangeAtIndex:1]];
263+
[regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
264+
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
263265
[(NSMutableArray *)argumentNames addObject:argumentName];
264266
}];
265267
} else {
@@ -270,14 +272,15 @@ - (instancetype)initWithMethodName:(NSString *)methodName
270272
}
271273

272274
// Extract class and method details
273-
_isClassMethod = [methodName characterAtIndex:0] == '+';
275+
_isClassMethod = [reactMethodName characterAtIndex:0] == '+';
274276
_moduleClass = NSClassFromString(_moduleClassName);
275277

276278
#if DEBUG
279+
277280
// Sanity check
278281
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
279282
@"You are attempting to export the method %@, but %@ does not \
280-
conform to the RCTBridgeModule Protocol", methodName, _moduleClassName);
283+
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
281284
#endif
282285

283286
// Get method signature
@@ -290,26 +293,26 @@ - (instancetype)initWithMethodName:(NSString *)methodName
290293
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
291294

292295
#define RCT_ARG_BLOCK(_logic) \
293-
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
294-
_logic \
295-
[invocation setArgument:&value atIndex:index]; \
296-
}]; \
296+
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
297+
_logic \
298+
[invocation setArgument:&value atIndex:index]; \
299+
}]; \
297300

298301
void (^addBlockArgument)(void) = ^{
299302
RCT_ARG_BLOCK(
300-
if (json && ![json isKindOfClass:[NSNumber class]]) {
301-
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
302-
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
303-
return;
304-
}
305-
306-
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
307-
__autoreleasing id value = (json ? ^(NSArray *args) {
308-
[bridge _invokeAndProcessModule:@"BatchedBridge"
309-
method:@"invokeCallbackAndReturnFlushedQueue"
310-
arguments:@[json, args]];
311-
} : ^(NSArray *unused) {});
312-
)
303+
if (json && ![json isKindOfClass:[NSNumber class]]) {
304+
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
305+
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
306+
return;
307+
}
308+
309+
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
310+
__autoreleasing id value = (json ? ^(NSArray *args) {
311+
[bridge _invokeAndProcessModule:@"BatchedBridge"
312+
method:@"invokeCallbackAndReturnFlushedQueue"
313+
arguments:@[json, args]];
314+
} : ^(NSArray *unused) {});
315+
)
313316
};
314317

315318
void (^defaultCase)(const char *) = ^(const char *argumentType) {
@@ -333,11 +336,11 @@ - (instancetype)initWithMethodName:(NSString *)methodName
333336
switch (argumentType[0]) {
334337

335338
#define RCT_CONVERT_CASE(_value, _type) \
336-
case _value: { \
337-
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
338-
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
339-
break; \
340-
}
339+
case _value: { \
340+
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
341+
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
342+
break; \
343+
}
341344

342345
RCT_CONVERT_CASE(':', SEL)
343346
RCT_CONVERT_CASE('*', const char *)
@@ -371,33 +374,33 @@ - (instancetype)initWithMethodName:(NSString *)methodName
371374
switch (argumentType[0]) {
372375

373376
#define RCT_CASE(_value, _class, _logic) \
374-
case _value: { \
375-
RCT_ARG_BLOCK( \
376-
if (json && ![json isKindOfClass:[_class class]]) { \
377-
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
378-
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
379-
return; \
380-
} \
381-
_logic \
382-
) \
383-
break; \
384-
}
377+
case _value: { \
378+
RCT_ARG_BLOCK( \
379+
if (json && ![json isKindOfClass:[_class class]]) { \
380+
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
381+
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
382+
return; \
383+
} \
384+
_logic \
385+
) \
386+
break; \
387+
}
385388

386389
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); )
387390
RCT_CASE('*', NSString, const char *value = [json UTF8String]; )
388391

389392
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
390-
case _value: { \
391-
RCT_ARG_BLOCK( \
392-
if (json && ![json respondsToSelector:@selector(_selector)]) { \
393-
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
394-
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
395-
return; \
396-
} \
397-
_type value = [json _selector]; \
398-
) \
399-
break; \
400-
}
393+
case _value: { \
394+
RCT_ARG_BLOCK( \
395+
if (json && ![json respondsToSelector:@selector(_selector)]) { \
396+
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
397+
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
398+
return; \
399+
} \
400+
_type value = [json _selector]; \
401+
) \
402+
break; \
403+
}
401404

402405
RCT_SIMPLE_CASE('c', char, charValue)
403406
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
@@ -432,6 +435,7 @@ - (void)invokeWithBridge:(RCTBridge *)bridge
432435
{
433436

434437
#if DEBUG
438+
435439
// Sanity check
436440
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
437441
%@ on a module of class %@", _methodName, [module class]);
@@ -496,15 +500,24 @@ - (NSString *)description
496500

497501
for (RCTHeaderValue addr = section->offset;
498502
addr < section->offset + section->size;
499-
addr += sizeof(const char **) * 2) {
503+
addr += sizeof(const char **) * 3) {
500504

501505
// Get data entry
502506
const char **entries = (const char **)(mach_header + addr);
503507

504508
// Create method
505-
RCTModuleMethod *moduleMethod =
506-
[[RCTModuleMethod alloc] initWithMethodName:@(entries[0])
507-
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
509+
RCTModuleMethod *moduleMethod;
510+
if (entries[2] == NULL) {
511+
512+
// Legacy support for RCT_EXPORT()
513+
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
514+
objCMethodName:@(entries[0])
515+
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
516+
} else {
517+
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
518+
objCMethodName:strlen(entries[1]) ? @(entries[1]) : nil
519+
JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil];
520+
}
508521

509522
// Cache method
510523
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
@@ -560,15 +573,15 @@ - (NSString *)description
560573
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
561574
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) {
562575
methodsByName[method.JSMethodName] = @{
563-
@"methodID": @(methodID),
564-
@"type": @"remote",
565-
};
576+
@"methodID": @(methodID),
577+
@"type": @"remote",
578+
};
566579
}];
567580

568581
NSDictionary *module = @{
569-
@"moduleID": @(moduleID),
570-
@"methods": methodsByName
571-
};
582+
@"moduleID": @(moduleID),
583+
@"methods": methodsByName
584+
};
572585

573586
remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module;
574587
}];
@@ -639,9 +652,9 @@ - (NSString *)description
639652
NSDictionary *module = localModules[moduleName];
640653
if (!module) {
641654
module = @{
642-
@"moduleID": @(localModules.count),
643-
@"methods": [[NSMutableDictionary alloc] init]
644-
};
655+
@"moduleID": @(localModules.count),
656+
@"methods": [[NSMutableDictionary alloc] init]
657+
};
645658
localModules[moduleName] = module;
646659
}
647660

@@ -650,9 +663,9 @@ - (NSString *)description
650663
NSMutableDictionary *methods = module[@"methods"];
651664
if (!methods[methodName]) {
652665
methods[methodName] = @{
653-
@"methodID": @(methods.count),
654-
@"type": @"local"
655-
};
666+
@"methodID": @(methods.count),
667+
@"type": @"local"
668+
};
656669
}
657670

658671
// Add module and method lookup
@@ -741,9 +754,9 @@ - (void)setUp
741754

742755
// Inject module data into JS context
743756
NSString *configJSON = RCTJSONStringify(@{
744-
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
745-
@"localModulesConfig": RCTLocalModulesConfig()
746-
}, NULL);
757+
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
758+
@"localModulesConfig": RCTLocalModulesConfig()
759+
}, NULL);
747760
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
748761
[_javaScriptExecutor injectJSONText:configJSON
749762
asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {

React/Base/RCTBridgeModule.h

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
3232
@property (nonatomic, weak) RCTBridge *bridge;
3333

3434
/**
35-
* Place this macro in your class implementation, to automatically register
35+
* Place this macro in your class implementation to automatically register
3636
* your module with the bridge when it loads. The optional js_name argument
3737
* will be used as the JS module name. If omitted, the JS module name will
3838
* match the Objective-C class name.
@@ -41,23 +41,11 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
4141
+ (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \
4242
))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; }
4343

44-
/**
45-
* Place this macro inside the method body of any method you want to expose
46-
* to JS. The optional js_name argument will be used as the JS method name
47-
* (the method will be namespaced to the module name, as specified above).
48-
* If omitted, the JS method name will match the first part of the Objective-C
49-
* method selector name (up to the first colon).
50-
*/
51-
#define RCT_EXPORT(js_name) \
52-
_Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
53-
__attribute__((used, section("__DATA,RCTExport"))) \
54-
static const char *__rct_export_entry__[] = { __func__, #js_name }
55-
5644
/**
5745
* Wrap the parameter line of your method implementation with this macro to
58-
* expose it to JS. Unlike the deprecated RCT_EXPORT, this macro does not take
59-
* a js_name argument and the exposed method will match the first part of the
60-
* Objective-C method selector name (up to the first colon).
46+
* expose it to JS. By default the exposed method will match the first part of
47+
* the Objective-C method selector name (up to the first colon). Use
48+
* RCT_REMAP_METHOD to specify the JS name of the method.
6149
*
6250
* For example, in ModuleName.m:
6351
*
@@ -74,12 +62,33 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
7462
* and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
7563
*/
7664
#define RCT_EXPORT_METHOD(method) \
65+
RCT_REMAP_METHOD(, method)
66+
67+
/**
68+
* Similar to RCT_EXPORT_METHOD but lets you set the JS name of the exported
69+
* method. Example usage:
70+
*
71+
* RCT_REMAP_METHOD(executeQueryWithParameters,
72+
* executeQuery:(NSString *)query parameters:(NSDictionary *)parameters)
73+
* { ... }
74+
*/
75+
#define RCT_REMAP_METHOD(js_name, method) \
7776
- (void)__rct_export__##method { \
7877
__attribute__((used, section("__DATA,RCTExport"))) \
79-
static const char *__rct_export_entry__[] = { __func__, #method }; \
78+
__attribute__((__aligned__(1))) \
79+
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
8080
} \
8181
- (void)method
8282

83+
/**
84+
* Deprecated, do not use.
85+
*/
86+
#define RCT_EXPORT(js_name) \
87+
_Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
88+
__attribute__((used, section("__DATA,RCTExport"))) \
89+
__attribute__((__aligned__(1))) \
90+
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
91+
8392
/**
8493
* Injects constants into JS. These constants are made accessible via
8594
* NativeModules.ModuleName.X. This method is called when the module is

React/Base/RCTConvert.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
135135
*/
136136
BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
137137

138+
/**
139+
* Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this.
140+
*/
141+
NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id);
142+
138143
#ifdef __cplusplus
139144
}
140145
#endif
@@ -169,13 +174,11 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter])
169174
/**
170175
* This macro is similar to RCT_CONVERTER, but specifically geared towards
171176
* numeric types. It will handle string input correctly, and provides more
172-
* detailed error reporting if a wrong value is passed in.
177+
* detailed error reporting if an invalid value is passed in.
173178
*/
174179
#define RCT_NUMBER_CONVERTER(type, getter) \
175180
RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
176181

177-
NSNumber *RCTEnumConverterImpl(const char *typeName, NSDictionary *values, NSNumber *defaultValue, id json);
178-
179182
/**
180183
* This macro is used for creating converters for enum types.
181184
*/
@@ -187,7 +190,7 @@ NSNumber *RCTEnumConverterImpl(const char *typeName, NSDictionary *values, NSNum
187190
dispatch_once(&onceToken, ^{ \
188191
mapping = values; \
189192
}); \
190-
NSNumber *converted = RCTEnumConverterImpl(#type, mapping, @(default), json); \
193+
NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \
191194
return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \
192195
}
193196

0 commit comments

Comments
 (0)