diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 0925633bd8495c..a6040bfe571d24 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -219,7 +219,8 @@ @implementation RCTModuleMethod 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 @@ @implementation RCTModuleMethod 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 @@ - (instancetype)initWithMethodName:(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 @@ - (instancetype)initWithMethodName:(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 @@ - (instancetype)initWithMethodName:(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 @@ - (instancetype)initWithMethodName:(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; - } - - // 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) {}); - ) + 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) {}); + ) }; void (^defaultCase)(const char *) = ^(const char *argumentType) { @@ -333,11 +336,11 @@ - (instancetype)initWithMethodName:(NSString *)methodName 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 @@ - (instancetype)initWithMethodName:(NSString *)methodName 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 @@ - (void)invokeWithBridge:(RCTBridge *)bridge { #if DEBUG + // Sanity check RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \ %@ on a module of class %@", _methodName, [module class]); @@ -496,15 +500,24 @@ - (NSString *)description 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 @@ - (NSString *)description 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 @@ - (NSString *)description 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 @@ - (NSString *)description 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 @@ - (void)setUp // 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) { diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index d7322ddbf575b1..70d5c76d092138 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -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 diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 0fbc8a84578465..e3f5e85980a0c5 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -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)); \ } diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index cb9cc7ec60b9e0..25de29656dc98a 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -115,12 +115,7 @@ + (NSDate *)NSDate:(id)json // 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 @@ static void logInvalidJSONObjectError(const char *typeName, id json, NSArray *ex 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 @@ static void logInvalidJSONObjectError(const char *typeName, id json, NSArray *ex } 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; }