diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h index a0602501cb..7a7f5b289d 100644 --- a/React/Base/RCTModuleData.h +++ b/React/Base/RCTModuleData.h @@ -44,6 +44,12 @@ typedef id(^RCTBridgeModuleProvider)(void); */ @property (nonatomic, copy, readonly) NSArray> *methods; +/** + * Returns a map of the module methods. Note that this will gather the methods the first + * time it is called and then memoize the results. + */ +@property (nonatomic, copy, readonly) NSDictionary> *methodsByName; + /** * Returns the module's constants, if it exports any */ diff --git a/React/Base/RCTModuleData.mm b/React/Base/RCTModuleData.mm index c02c8810f6..f52d3ae107 100644 --- a/React/Base/RCTModuleData.mm +++ b/React/Base/RCTModuleData.mm @@ -28,6 +28,7 @@ } @synthesize methods = _methods; +@synthesize methodsByName = _methodsByName; @synthesize instance = _instance; @synthesize methodQueue = _methodQueue; @@ -225,6 +226,47 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); } } +- (void) calculateMethods +{ + if (_methods && _methodsByName) { + return; + } + + NSMutableArray> *moduleMethods = [NSMutableArray new]; + NSMutableDictionary> *moduleMethodsByName = [NSMutableDictionary new]; + + if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) { + [moduleMethods addObjectsFromArray:[self.instance methodsToExport]]; + } + + unsigned int methodCount; + Class cls = _moduleClass; + while (cls && cls != [NSObject class] && cls != [NSProxy class]) { + Method *methods = class_copyMethodList(object_getClass(cls), &methodCount); + + for (unsigned int i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) { + IMP imp = method_getImplementation(method); + auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_moduleClass, selector); + id moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod + moduleClass:_moduleClass]; + + NSString *str = [NSString stringWithUTF8String:moduleMethod.JSMethodName]; + [moduleMethodsByName setValue:moduleMethod forKey:str]; + [moduleMethods addObject:moduleMethod]; + } + } + + free(methods); + cls = class_getSuperclass(cls); + } + + _methods = [moduleMethods copy]; + _methodsByName = [moduleMethodsByName copy]; +} + #pragma mark - public getters - (BOOL)hasInstance @@ -267,37 +309,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); - (NSArray> *)methods { - if (!_methods) { - NSMutableArray> *moduleMethods = [NSMutableArray new]; + [self calculateMethods]; + return _methods; +} - if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) { - [moduleMethods addObjectsFromArray:[self.instance methodsToExport]]; - } - - unsigned int methodCount; - Class cls = _moduleClass; - while (cls && cls != [NSObject class] && cls != [NSProxy class]) { - Method *methods = class_copyMethodList(object_getClass(cls), &methodCount); - - for (unsigned int i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) { - IMP imp = method_getImplementation(method); - auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_moduleClass, selector); - id moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod - moduleClass:_moduleClass]; - [moduleMethods addObject:moduleMethod]; - } - } - - free(methods); - cls = class_getSuperclass(cls); - } - - _methods = [moduleMethods copy]; - } - return _methods; +- (NSDictionary> *)methodsByName +{ + [self calculateMethods]; + return _methodsByName; } - (void)gatherConstants diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index d40995bc30..1d7ac52a94 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1060,7 +1060,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po } RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag - commandID:(NSInteger)commandID + commandID:(id /*(NSString or NSNumber) */)commandID commandArgs:(NSArray *)commandArgs) { RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; @@ -1086,7 +1086,19 @@ RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag Class managerClass = componentData.managerClass; RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)]; - id method = moduleData.methods[commandID]; + + id method; + if ([commandID isKindOfClass:[NSNumber class]]) { + method = moduleData.methods[[commandID intValue]]; + } else if([commandID isKindOfClass:[NSString class]]) { + method = moduleData.methodsByName[commandID]; + if (method == nil) { + RCTLogError(@"No command found with name \"%@\"", commandID); + } + } else { + RCTLogError(@"dispatchViewManagerCommand must be called with a string or integer command"); + return; + } NSArray *args = [@[reactTag] arrayByAddingObjectsFromArray:commandArgs]; [method invokeWithBridge:_bridge module:componentData.manager arguments:args];