[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 https://github.com/facebook/react-native/pull/802 Github Author: James Ide <ide@jameside.com> Test Plan: Imported from GitHub, without a `Test Plan:` line.
This commit is contained in:
parent
5bbb351816
commit
e193a13ef7
|
@ -219,7 +219,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
|||
|
||||
static Class _globalExecutorClass;
|
||||
|
||||
NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
|
||||
static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
{
|
||||
NSRange colonRange = [methodName rangeOfString:@":"];
|
||||
if (colonRange.length) {
|
||||
methodName = [methodName substringToIndex:colonRange.location];
|
||||
|
@ -227,12 +228,13 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
|
|||
return methodName;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMethodName:(NSString *)methodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
- (instancetype)initWithReactMethodName:(NSString *)reactMethodName
|
||||
objCMethodName:(NSString *)objCMethodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_methodName = methodName;
|
||||
NSArray *parts = [[methodName substringWithRange:(NSRange){2, methodName.length - 3}] componentsSeparatedByString:@" "];
|
||||
_methodName = reactMethodName;
|
||||
NSArray *parts = [[reactMethodName substringWithRange:(NSRange){2, reactMethodName.length - 3}] componentsSeparatedByString:@" "];
|
||||
|
||||
// Parse class and method
|
||||
_moduleClassName = parts[0];
|
||||
|
@ -246,7 +248,7 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
|
|||
// New format
|
||||
NSString *selectorString = [parts[1] substringFromIndex:14];
|
||||
_selector = NSSelectorFromString(selectorString);
|
||||
_JSMethodName = RCTStringUpToFirstArgument(selectorString);
|
||||
_JSMethodName = JSMethodName ?: RCTStringUpToFirstArgument(selectorString);
|
||||
|
||||
static NSRegularExpression *regExp;
|
||||
if (!regExp) {
|
||||
|
@ -258,8 +260,8 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
|
|||
}
|
||||
|
||||
argumentNames = [NSMutableArray array];
|
||||
[regExp enumerateMatchesInString:JSMethodName options:0 range:NSMakeRange(0, JSMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSString *argumentName = [JSMethodName substringWithRange:[result rangeAtIndex:1]];
|
||||
[regExp enumerateMatchesInString:objCMethodName options:0 range:NSMakeRange(0, objCMethodName.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSString *argumentName = [objCMethodName substringWithRange:[result rangeAtIndex:1]];
|
||||
[(NSMutableArray *)argumentNames addObject:argumentName];
|
||||
}];
|
||||
} else {
|
||||
|
@ -270,14 +272,15 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
|
|||
}
|
||||
|
||||
// Extract class and method details
|
||||
_isClassMethod = [methodName characterAtIndex:0] == '+';
|
||||
_isClassMethod = [reactMethodName characterAtIndex:0] == '+';
|
||||
_moduleClass = NSClassFromString(_moduleClassName);
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// Sanity check
|
||||
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
|
||||
@"You are attempting to export the method %@, but %@ does not \
|
||||
conform to the RCTBridgeModule Protocol", methodName, _moduleClassName);
|
||||
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
|
||||
#endif
|
||||
|
||||
// Get method signature
|
||||
|
@ -290,26 +293,26 @@ NS_INLINE NSString *RCTStringUpToFirstArgument(NSString *methodName) {
|
|||
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
|
||||
|
||||
#define RCT_ARG_BLOCK(_logic) \
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
_logic \
|
||||
[invocation setArgument:&value atIndex:index]; \
|
||||
}]; \
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
_logic \
|
||||
[invocation setArgument:&value atIndex:index]; \
|
||||
}]; \
|
||||
|
||||
void (^addBlockArgument)(void) = ^{
|
||||
RCT_ARG_BLOCK(
|
||||
if (json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
if (json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing id value = (json ? ^(NSArray *args) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, args]];
|
||||
} : ^(NSArray *unused) {});
|
||||
)
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing id value = (json ? ^(NSArray *args) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, args]];
|
||||
} : ^(NSArray *unused) {});
|
||||
)
|
||||
};
|
||||
|
||||
void (^defaultCase)(const char *) = ^(const char *argumentType) {
|
||||
|
@ -333,11 +336,11 @@ _logic \
|
|||
switch (argumentType[0]) {
|
||||
|
||||
#define RCT_CONVERT_CASE(_value, _type) \
|
||||
case _value: { \
|
||||
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
|
||||
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
|
||||
break; \
|
||||
}
|
||||
case _value: { \
|
||||
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
|
||||
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
|
||||
break; \
|
||||
}
|
||||
|
||||
RCT_CONVERT_CASE(':', SEL)
|
||||
RCT_CONVERT_CASE('*', const char *)
|
||||
|
@ -371,33 +374,33 @@ break; \
|
|||
switch (argumentType[0]) {
|
||||
|
||||
#define RCT_CASE(_value, _class, _logic) \
|
||||
case _value: { \
|
||||
RCT_ARG_BLOCK( \
|
||||
if (json && ![json isKindOfClass:[_class class]]) { \
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
|
||||
return; \
|
||||
} \
|
||||
_logic \
|
||||
) \
|
||||
break; \
|
||||
}
|
||||
case _value: { \
|
||||
RCT_ARG_BLOCK( \
|
||||
if (json && ![json isKindOfClass:[_class class]]) { \
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
|
||||
return; \
|
||||
} \
|
||||
_logic \
|
||||
) \
|
||||
break; \
|
||||
}
|
||||
|
||||
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); )
|
||||
RCT_CASE('*', NSString, const char *value = [json UTF8String]; )
|
||||
|
||||
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
|
||||
case _value: { \
|
||||
RCT_ARG_BLOCK( \
|
||||
if (json && ![json respondsToSelector:@selector(_selector)]) { \
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
|
||||
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
|
||||
return; \
|
||||
} \
|
||||
_type value = [json _selector]; \
|
||||
) \
|
||||
break; \
|
||||
}
|
||||
case _value: { \
|
||||
RCT_ARG_BLOCK( \
|
||||
if (json && ![json respondsToSelector:@selector(_selector)]) { \
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
|
||||
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
|
||||
return; \
|
||||
} \
|
||||
_type value = [json _selector]; \
|
||||
) \
|
||||
break; \
|
||||
}
|
||||
|
||||
RCT_SIMPLE_CASE('c', char, charValue)
|
||||
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
|
||||
|
@ -432,6 +435,7 @@ break; \
|
|||
{
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// Sanity check
|
||||
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
|
||||
%@ on a module of class %@", _methodName, [module class]);
|
||||
|
@ -496,15 +500,24 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
|
|||
|
||||
for (RCTHeaderValue addr = section->offset;
|
||||
addr < section->offset + section->size;
|
||||
addr += sizeof(const char **) * 2) {
|
||||
addr += sizeof(const char **) * 3) {
|
||||
|
||||
// Get data entry
|
||||
const char **entries = (const char **)(mach_header + addr);
|
||||
|
||||
// Create method
|
||||
RCTModuleMethod *moduleMethod =
|
||||
[[RCTModuleMethod alloc] initWithMethodName:@(entries[0])
|
||||
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
|
||||
RCTModuleMethod *moduleMethod;
|
||||
if (entries[2] == NULL) {
|
||||
|
||||
// Legacy support for RCT_EXPORT()
|
||||
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
|
||||
objCMethodName:@(entries[0])
|
||||
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
|
||||
} else {
|
||||
moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0])
|
||||
objCMethodName:strlen(entries[1]) ? @(entries[1]) : nil
|
||||
JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil];
|
||||
}
|
||||
|
||||
// Cache method
|
||||
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
|
||||
|
@ -560,15 +573,15 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
|
|||
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
|
||||
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) {
|
||||
methodsByName[method.JSMethodName] = @{
|
||||
@"methodID": @(methodID),
|
||||
@"type": @"remote",
|
||||
};
|
||||
@"methodID": @(methodID),
|
||||
@"type": @"remote",
|
||||
};
|
||||
}];
|
||||
|
||||
NSDictionary *module = @{
|
||||
@"moduleID": @(moduleID),
|
||||
@"methods": methodsByName
|
||||
};
|
||||
@"moduleID": @(moduleID),
|
||||
@"methods": methodsByName
|
||||
};
|
||||
|
||||
remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module;
|
||||
}];
|
||||
|
@ -639,9 +652,9 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
NSDictionary *module = localModules[moduleName];
|
||||
if (!module) {
|
||||
module = @{
|
||||
@"moduleID": @(localModules.count),
|
||||
@"methods": [[NSMutableDictionary alloc] init]
|
||||
};
|
||||
@"moduleID": @(localModules.count),
|
||||
@"methods": [[NSMutableDictionary alloc] init]
|
||||
};
|
||||
localModules[moduleName] = module;
|
||||
}
|
||||
|
||||
|
@ -650,9 +663,9 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
NSMutableDictionary *methods = module[@"methods"];
|
||||
if (!methods[methodName]) {
|
||||
methods[methodName] = @{
|
||||
@"methodID": @(methods.count),
|
||||
@"type": @"local"
|
||||
};
|
||||
@"methodID": @(methods.count),
|
||||
@"type": @"local"
|
||||
};
|
||||
}
|
||||
|
||||
// Add module and method lookup
|
||||
|
@ -741,9 +754,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
// Inject module data into JS context
|
||||
NSString *configJSON = RCTJSONStringify(@{
|
||||
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
|
||||
@"localModulesConfig": RCTLocalModulesConfig()
|
||||
}, NULL);
|
||||
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
|
||||
@"localModulesConfig": RCTLocalModulesConfig()
|
||||
}, NULL);
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
[_javaScriptExecutor injectJSONText:configJSON
|
||||
asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
|
||||
|
|
|
@ -32,7 +32,7 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
|||
@property (nonatomic, weak) RCTBridge *bridge;
|
||||
|
||||
/**
|
||||
* Place this macro in your class implementation, to automatically register
|
||||
* Place this macro in your class implementation to automatically register
|
||||
* your module with the bridge when it loads. The optional js_name argument
|
||||
* will be used as the JS module name. If omitted, the JS module name will
|
||||
* match the Objective-C class name.
|
||||
|
@ -41,23 +41,11 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
|||
+ (NSString *)moduleName { __attribute__((used, section("__DATA,RCTExportModule" \
|
||||
))) static const char *__rct_export_entry__ = { __func__ }; return @#js_name; }
|
||||
|
||||
/**
|
||||
* Place this macro inside the method body of any method you want to expose
|
||||
* to JS. The optional js_name argument will be used as the JS method name
|
||||
* (the method will be namespaced to the module name, as specified above).
|
||||
* If omitted, the JS method name will match the first part of the Objective-C
|
||||
* method selector name (up to the first colon).
|
||||
*/
|
||||
#define RCT_EXPORT(js_name) \
|
||||
_Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
|
||||
__attribute__((used, section("__DATA,RCTExport"))) \
|
||||
static const char *__rct_export_entry__[] = { __func__, #js_name }
|
||||
|
||||
/**
|
||||
* Wrap the parameter line of your method implementation with this macro to
|
||||
* expose it to JS. Unlike the deprecated RCT_EXPORT, this macro does not take
|
||||
* a js_name argument and the exposed method will match the first part of the
|
||||
* Objective-C method selector name (up to the first colon).
|
||||
* expose it to JS. By default the exposed method will match the first part of
|
||||
* the Objective-C method selector name (up to the first colon). Use
|
||||
* RCT_REMAP_METHOD to specify the JS name of the method.
|
||||
*
|
||||
* For example, in ModuleName.m:
|
||||
*
|
||||
|
@ -74,12 +62,33 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
|||
* and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
|
||||
*/
|
||||
#define RCT_EXPORT_METHOD(method) \
|
||||
RCT_REMAP_METHOD(, method)
|
||||
|
||||
/**
|
||||
* Similar to RCT_EXPORT_METHOD but lets you set the JS name of the exported
|
||||
* method. Example usage:
|
||||
*
|
||||
* RCT_REMAP_METHOD(executeQueryWithParameters,
|
||||
* executeQuery:(NSString *)query parameters:(NSDictionary *)parameters)
|
||||
* { ... }
|
||||
*/
|
||||
#define RCT_REMAP_METHOD(js_name, method) \
|
||||
- (void)__rct_export__##method { \
|
||||
__attribute__((used, section("__DATA,RCTExport"))) \
|
||||
static const char *__rct_export_entry__[] = { __func__, #method }; \
|
||||
__attribute__((__aligned__(1))) \
|
||||
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
|
||||
} \
|
||||
- (void)method
|
||||
|
||||
/**
|
||||
* Deprecated, do not use.
|
||||
*/
|
||||
#define RCT_EXPORT(js_name) \
|
||||
_Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \
|
||||
__attribute__((used, section("__DATA,RCTExport"))) \
|
||||
__attribute__((__aligned__(1))) \
|
||||
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
|
||||
|
||||
/**
|
||||
* Injects constants into JS. These constants are made accessible via
|
||||
* NativeModules.ModuleName.X. This method is called when the module is
|
||||
|
|
|
@ -135,6 +135,11 @@ BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
|
|||
*/
|
||||
BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
|
||||
|
||||
/**
|
||||
* Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this.
|
||||
*/
|
||||
NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -169,13 +174,11 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter])
|
|||
/**
|
||||
* This macro is similar to RCT_CONVERTER, but specifically geared towards
|
||||
* numeric types. It will handle string input correctly, and provides more
|
||||
* detailed error reporting if a wrong value is passed in.
|
||||
* detailed error reporting if an invalid value is passed in.
|
||||
*/
|
||||
#define RCT_NUMBER_CONVERTER(type, getter) \
|
||||
RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
|
||||
|
||||
NSNumber *RCTEnumConverterImpl(const char *typeName, NSDictionary *values, NSNumber *defaultValue, id json);
|
||||
|
||||
/**
|
||||
* This macro is used for creating converters for enum types.
|
||||
*/
|
||||
|
@ -187,7 +190,7 @@ NSNumber *RCTEnumConverterImpl(const char *typeName, NSDictionary *values, NSNum
|
|||
dispatch_once(&onceToken, ^{ \
|
||||
mapping = values; \
|
||||
}); \
|
||||
NSNumber *converted = RCTEnumConverterImpl(#type, mapping, @(default), json); \
|
||||
NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \
|
||||
return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \
|
||||
}
|
||||
|
||||
|
|
|
@ -115,12 +115,7 @@ RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0
|
|||
// JS standard for time zones is minutes.
|
||||
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
|
||||
|
||||
static void logInvalidJSONObjectError(const char *typeName, id json, NSArray *expectedValues)
|
||||
{
|
||||
RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, expectedValues);
|
||||
}
|
||||
|
||||
NSNumber *RCTEnumConverterImpl(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
|
||||
NSNumber *RCTConverterEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
|
||||
{
|
||||
if (!json || json == (id)kCFNull) {
|
||||
return defaultValue;
|
||||
|
@ -130,7 +125,7 @@ NSNumber *RCTEnumConverterImpl(const char *typeName, NSDictionary *mapping, NSNu
|
|||
if ([[mapping allValues] containsObject:json] || [json isEqual:defaultValue]) {
|
||||
return json;
|
||||
}
|
||||
logInvalidJSONObjectError(typeName, json, allValues);
|
||||
RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, allValues);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
@ -140,7 +135,7 @@ NSNumber *RCTEnumConverterImpl(const char *typeName, NSDictionary *mapping, NSNu
|
|||
}
|
||||
id value = mapping[json];
|
||||
if (!value && [json description].length > 0) {
|
||||
logInvalidJSONObjectError(typeName, json, [mapping allKeys]);
|
||||
RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, [mapping allKeys]);
|
||||
}
|
||||
return value ?: defaultValue;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue