1// Copyright © 2018 650 Industries. All rights reserved. 2 3#import <objc/runtime.h> 4 5#import <ExpoModulesCore/EXExportedModule.h> 6 7#define QUOTE(str) #str 8#define EXPAND_AND_QUOTE(str) QUOTE(str) 9 10#define EX_IS_METHOD_EXPORTED(methodName) \ 11[methodName hasPrefix:@EXPAND_AND_QUOTE(EX_EXPORTED_METHODS_PREFIX)] 12 13static const NSString *noNameExceptionName = @"No custom +(const NSString *)exportedModuleName implementation."; 14static const NSString *noNameExceptionReasonFormat = @"You've subclassed an EXExportedModule in %@, but didn't override the +(const NSString *)exportedModuleName method. Override this method and return a name for your exported module."; 15 16static const NSRegularExpression *selectorRegularExpression = nil; 17static dispatch_once_t selectorRegularExpressionOnceToken = 0; 18 19@interface EXExportedModule () 20 21@property (nonatomic, strong) dispatch_queue_t methodQueue; 22@property (nonatomic, strong) NSDictionary<NSString *, NSString *> *exportedMethods; 23 24@end 25 26@implementation EXExportedModule 27 28- (instancetype)init 29{ 30 return self = [super init]; 31} 32 33- (instancetype)copyWithZone:(NSZone *)zone 34{ 35 return self; 36} 37 38+ (const NSArray<Protocol *> *)exportedInterfaces { 39 return nil; 40} 41 42 43+ (const NSString *)exportedModuleName 44{ 45 NSString *reason = [NSString stringWithFormat:(NSString *)noNameExceptionReasonFormat, [self description]]; 46 @throw [NSException exceptionWithName:(NSString *)noNameExceptionName 47 reason:reason 48 userInfo:nil]; 49} 50 51- (NSDictionary *)constantsToExport 52{ 53 return nil; 54} 55 56- (dispatch_queue_t)methodQueue 57{ 58 if (!_methodQueue) { 59 NSString *queueName = [NSString stringWithFormat:@"expo.modules.%@Queue", [[self class] exportedModuleName]]; 60 _methodQueue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL); 61 } 62 return _methodQueue; 63} 64 65# pragma mark - Exported methods 66 67- (NSDictionary<NSString *, NSString *> *)getExportedMethods 68{ 69 if (_exportedMethods) { 70 return _exportedMethods; 71 } 72 73 NSMutableDictionary<NSString *, NSString *> *exportedMethods = [NSMutableDictionary dictionary]; 74 75 Class klass = [self class]; 76 77 while (klass) { 78 unsigned int methodsCount; 79 Method *methodsDescriptions = class_copyMethodList(object_getClass(klass), &methodsCount); 80 81 @try { 82 for(int i = 0; i < methodsCount; i++) { 83 Method method = methodsDescriptions[i]; 84 SEL methodSelector = method_getName(method); 85 NSString *methodName = NSStringFromSelector(methodSelector); 86 if (EX_IS_METHOD_EXPORTED(methodName)) { 87 IMP imp = method_getImplementation(method); 88 const EXMethodInfo *info = ((const EXMethodInfo *(*)(id, SEL))imp)(klass, methodSelector); 89 NSString *fullSelectorName = [NSString stringWithUTF8String:info->objcName]; 90 // `objcName` constains a method declaration string 91 // (eg. `doSth:(NSString *)string options:(NSDictionary *)options`) 92 // We only need a selector string (eg. `doSth:options:`) 93 NSString *simpleSelectorName = [self selectorNameFromName:fullSelectorName]; 94 exportedMethods[[NSString stringWithUTF8String:info->jsName]] = simpleSelectorName; 95 } 96 } 97 } 98 @finally { 99 free(methodsDescriptions); 100 } 101 102 klass = [klass superclass]; 103 } 104 105 _exportedMethods = exportedMethods; 106 107 return _exportedMethods; 108} 109 110- (NSString *)selectorNameFromName:(NSString *)nameString 111{ 112 dispatch_once(&selectorRegularExpressionOnceToken, ^{ 113 selectorRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"\\(.+?\\).+?\\b\\s*" options:NSRegularExpressionCaseInsensitive error:nil]; 114 }); 115 return [selectorRegularExpression stringByReplacingMatchesInString:nameString options:0 range:NSMakeRange(0, [nameString length]) withTemplate:@""]; 116} 117 118static const NSNumber *trueValue; 119 120- (void)callExportedMethod:(NSString *)methodName withArguments:(NSArray *)arguments resolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject 121{ 122 trueValue = [NSNumber numberWithBool:YES]; 123 const NSString *moduleName = [[self class] exportedModuleName]; 124 NSString *methodDeclaration = _exportedMethods[methodName]; 125 if (methodDeclaration == nil) { 126 NSString *reason = [NSString stringWithFormat:@"Module '%@' does not export method '%@'.", moduleName, methodName]; 127 reject(@"E_NO_METHOD", reason, nil); 128 return; 129 } 130 SEL selector = NSSelectorFromString(methodDeclaration); 131 NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; 132 if (methodSignature == nil) { 133 // This in fact should never happen -- if we have a methodDeclaration for an exported method 134 // it means that it has been exported with EX_IMPORT_METHOD and if we cannot find method signature 135 // for the cached selector either the macro or the -selectorNameFromName is faulty. 136 NSString *reason = [NSString stringWithFormat:@"Module '%@' does not implement method for selector '%@'.", moduleName, NSStringFromSelector(selector)]; 137 reject(@"E_NO_METHOD", reason, nil); 138 return; 139 } 140 141 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; 142 [invocation setTarget:self]; 143 [invocation setSelector:selector]; 144 [arguments enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 145 if (obj != [NSNull null]) { 146 [invocation setArgument:&obj atIndex:(2 + idx)]; 147 } 148 149 // According to objc.h, the BOOL type can be represented by `bool` or `signed char` so 150 // getArgumentTypeAtIndex can return _C_BOOL (when `bool`) or _C_CHR (when `signed char`) 151#if OBJC_BOOL_IS_BOOL 152 if ([methodSignature getArgumentTypeAtIndex:(2 + idx)][0] == _C_BOOL) { 153 // We need this intermediary variable, see 154 // https://stackoverflow.com/questions/11061166/pointer-to-bool-in-objective-c 155 BOOL value = [obj boolValue]; 156 [invocation setArgument:&value atIndex:(2 + idx)]; 157 } 158#else // BOOL is represented by `signed char` 159 if ([methodSignature getArgumentTypeAtIndex:(2 + idx)][0] == _C_CHR){ 160 BOOL value = [obj charValue]; 161 [invocation setArgument:&value atIndex:(2 + idx)]; 162 } 163#endif 164 }]; 165 [invocation setArgument:&resolve atIndex:(2 + [arguments count])]; 166 [invocation setArgument:&reject atIndex:([arguments count] + 2 + 1)]; 167 [invocation retainArguments]; 168 [invocation invoke]; 169} 170 171@end 172