// Copyright 2018-present 650 Industries. All rights reserved. #import #import #import #import #import #import #import static const NSString *exportedMethodsNamesKeyPath = @"exportedMethods"; static const NSString *viewManagersNamesKeyPath = @"viewManagersNames"; static const NSString *exportedConstantsKeyPath = @"modulesConstants"; static const NSString *methodInfoKeyKey = @"key"; static const NSString *methodInfoNameKey = @"name"; static const NSString *methodInfoArgumentsCountKey = @"argumentsCount"; @interface UMNativeModulesProxy () @property (nonatomic, strong) NSRegularExpression *regexp; @property (nonatomic, strong) UMModuleRegistry *umModuleRegistry; @property (nonatomic, strong) NSMutableDictionary *> *exportedMethodsKeys; @property (nonatomic, strong) NSMutableDictionary *> *exportedMethodsReverseKeys; @end @implementation UMNativeModulesProxy - (instancetype)initWithModuleRegistry:(UMModuleRegistry *)moduleRegistry { if (self = [super init]) { _umModuleRegistry = moduleRegistry; _exportedMethodsKeys = [NSMutableDictionary dictionary]; _exportedMethodsReverseKeys = [NSMutableDictionary dictionary]; } return self; } + (const NSString *)moduleName { return @"NativeUnimoduleProxy"; } # pragma mark - React API + (BOOL)requiresMainQueueSetup { return YES; } - (NSDictionary *)constantsToExport { NSMutableDictionary *exportedModulesConstants = [NSMutableDictionary dictionary]; // Grab all the constants exported by modules for (UMExportedModule *exportedModule in [_umModuleRegistry getAllExportedModules]) { @try { exportedModulesConstants[[[exportedModule class] exportedModuleName]] = [exportedModule constantsToExport] ?: [NSNull null]; } @catch (NSException *exception) { continue; } } // Also add `exportedMethodsNames` NSMutableDictionary *> *> *exportedMethodsNamesAccumulator = [NSMutableDictionary dictionary]; for (UMExportedModule *exportedModule in [_umModuleRegistry getAllExportedModules]) { const NSString *exportedModuleName = [[exportedModule class] exportedModuleName]; exportedMethodsNamesAccumulator[exportedModuleName] = [NSMutableArray array]; [[exportedModule getExportedMethods] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull exportedName, NSString * _Nonnull selectorName, BOOL * _Nonnull stop) { NSMutableDictionary *methodInfo = [NSMutableDictionary dictionaryWithDictionary:@{ methodInfoNameKey: exportedName, // - 3 is for resolver and rejecter of the promise and the last, empty component methodInfoArgumentsCountKey: @([[selectorName componentsSeparatedByString:@":"] count] - 3) }]; [exportedMethodsNamesAccumulator[exportedModuleName] addObject:methodInfo]; }]; [self assignExportedMethodsKeys:exportedMethodsNamesAccumulator[exportedModuleName] forModuleName:exportedModuleName]; } // Also, add `viewManagersNames` for sanity check and testing purposes -- with names we know what managers to mock on UIManager NSArray *viewManagers = [_umModuleRegistry getAllViewManagers]; NSMutableArray *viewManagersNames = [NSMutableArray arrayWithCapacity:[viewManagers count]]; for (UMViewManager *viewManager in viewManagers) { [viewManagersNames addObject:[viewManager viewName]]; } NSMutableDictionary *constantsAccumulator = [NSMutableDictionary dictionary]; constantsAccumulator[viewManagersNamesKeyPath] = viewManagersNames; constantsAccumulator[exportedConstantsKeyPath] = exportedModulesConstants; constantsAccumulator[exportedMethodsNamesKeyPath] = exportedMethodsNamesAccumulator; return constantsAccumulator; } RCT_EXPORT_METHOD(callMethod:(NSString *)moduleName methodNameOrKey:(id)methodNameOrKey arguments:(NSArray *)arguments resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { UMExportedModule *module = [_umModuleRegistry getExportedModuleForName:moduleName]; if (module == nil) { NSString *reason = [NSString stringWithFormat:@"No exported module was found for name '%@'. Are you sure all the packages are linked correctly?", moduleName]; reject(@"E_NO_MODULE", reason, nil); return; } if (!methodNameOrKey) { reject(@"E_NO_METHOD", @"No method key or name provided", nil); return; } NSString *methodName; if ([methodNameOrKey isKindOfClass:[NSString class]]) { methodName = (NSString *)methodNameOrKey; } else if ([methodNameOrKey isKindOfClass:[NSNumber class]]) { methodName = _exportedMethodsReverseKeys[moduleName][(NSNumber *)methodNameOrKey]; } else { reject(@"E_INV_MKEY", @"Method key is neither a String nor an Integer -- don't know how to map it to method name.", nil); return; } dispatch_async([module methodQueue], ^{ @try { [module callExportedMethod:methodName withArguments:arguments resolver:resolve rejecter:reject]; } @catch (NSException *e) { NSString *message = [NSString stringWithFormat:@"An exception was thrown while calling `%@.%@` with arguments `%@`: %@", moduleName, methodName, arguments, e]; reject(@"E_EXC", message, nil); } }); } - (void)assignExportedMethodsKeys:(NSMutableArray *> *)exportedMethods forModuleName:(const NSString *)moduleName { if (!_exportedMethodsKeys[moduleName]) { _exportedMethodsKeys[moduleName] = [NSMutableDictionary dictionary]; } if (!_exportedMethodsReverseKeys[moduleName]) { _exportedMethodsReverseKeys[moduleName] = [NSMutableDictionary dictionary]; } for (int i = 0; i < [exportedMethods count]; i++) { NSMutableDictionary *methodInfo = exportedMethods[i]; if (!methodInfo[(NSString *)methodInfoNameKey] || ![methodInfo[methodInfoNameKey] isKindOfClass:[NSString class]]) { NSString *reason = [NSString stringWithFormat:@"Method info of a method of module %@ has no method name.", moduleName]; @throw [NSException exceptionWithName:@"Empty method name in method info" reason:reason userInfo:nil]; } NSString *methodName = methodInfo[(NSString *)methodInfoNameKey]; NSNumber *previousMethodKey = _exportedMethodsKeys[moduleName][methodName]; if (previousMethodKey) { methodInfo[methodInfoKeyKey] = previousMethodKey; } else { NSNumber *newKey = @([[_exportedMethodsKeys[moduleName] allValues] count]); methodInfo[methodInfoKeyKey] = newKey; _exportedMethodsKeys[moduleName][methodName] = newKey; _exportedMethodsReverseKeys[moduleName][newKey] = methodName; } } } @end