Simplify -[RCTModuleMethod processMethodSignature]

Reviewed By: fromcelticpark

Differential Revision: D5397371

fbshipit-source-id: d341d55fd8bd1a67a0980543f8defedfd12b5dd4
This commit is contained in:
Pieter De Baets 2017-07-24 06:46:03 -07:00 коммит произвёл Facebook Github Bot
Родитель cb12080179
Коммит 98258b437c
1 изменённых файлов: 150 добавлений и 185 удалений

Просмотреть файл

@ -103,7 +103,8 @@ static RCTNullability RCTParseNullabilityPostfix(const char **input)
}
// returns YES if execution is safe to proceed (enqueue callback invocation), NO if callback has already been invoked
static BOOL RCTCheckCallbackMultipleInvocations(BOOL *didInvoke) {
#if RCT_DEBUG
static BOOL checkCallbackMultipleInvocations(BOOL *didInvoke) {
if (*didInvoke) {
RCTFatal(RCTErrorWithMessage(@"Illegal callback invocation from native module. This callback type only permits a single invocation from native code."));
return NO;
@ -112,6 +113,7 @@ static BOOL RCTCheckCallbackMultipleInvocations(BOOL *didInvoke) {
return YES;
}
}
#endif
SEL RCTParseMethodSignature(const char *, NSArray<RCTMethodArgument *> **);
SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **arguments)
@ -190,36 +192,47 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
NSMutableArray<RCTArgumentBlock> *argumentBlocks =
[[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#if RCT_DEBUG
__weak RCTModuleMethod *weakSelf = self;
#endif
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:(index) + 2]; \
return YES; \
}];
_logic \
[invocation setArgument:&value atIndex:(index) + 2]; \
return YES; \
}]
/**
* Explicitly copy the block and retain it, since NSInvocation doesn't retain them.
*/
#define RCT_BLOCK_ARGUMENT(block...) \
id value = json ? [block copy] : (id)^(__unused NSArray *_){}; \
#define __PRIMITIVE_CASE(_type, _nullable) { \
isNullableType = _nullable; \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ); \
break; \
}
#define PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, NO)
#define NULLABLE_PRIMITIVE_CASE(_type) __PRIMITIVE_CASE(_type, YES)
// Explicitly copy the block and retain it, since NSInvocation doesn't retain them
#define __COPY_BLOCK(block...) \
id value = [block copy]; \
CFBridgingRetain(value)
__weak RCTModuleMethod *weakSelf = self;
void (^addBlockArgument)(void) = ^{
RCT_ARG_BLOCK(
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogArgumentError(weakSelf, index, json, "should be a function");
return NO;
}
__block BOOL didInvoke = NO;
RCT_BLOCK_ARGUMENT(^(NSArray *args) {
if (RCTCheckCallbackMultipleInvocations(&didInvoke)) {
[bridge enqueueCallback:json args:args];
}
});
)
};
#if RCT_DEBUG
#define BLOCK_CASE(_block_args, _block) RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[NSNumber class]]) { \
RCTLogArgumentError(weakSelf, index, json, "should be a function"); \
return NO; \
} \
__block BOOL didInvoke = NO; \
__COPY_BLOCK(^_block_args { \
if (checkCallbackMultipleInvocations(&didInvoke)) _block \
}); \
)
#else
#define BLOCK_CASE(_block_args, _block) \
RCT_ARG_BLOCK( __COPY_BLOCK(^_block_args { _block }); )
#endif
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
@ -229,39 +242,23 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
SEL selector = RCTConvertSelectorForType(typeName);
if ([RCTConvert respondsToSelector:selector]) {
switch (objcType[0]) {
#define RCT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \
}
RCT_CASE(_C_CHR, char)
RCT_CASE(_C_UCHR, unsigned char)
RCT_CASE(_C_SHT, short)
RCT_CASE(_C_USHT, unsigned short)
RCT_CASE(_C_INT, int)
RCT_CASE(_C_UINT, unsigned int)
RCT_CASE(_C_LNG, long)
RCT_CASE(_C_ULNG, unsigned long)
RCT_CASE(_C_LNG_LNG, long long)
RCT_CASE(_C_ULNG_LNG, unsigned long long)
RCT_CASE(_C_FLT, float)
RCT_CASE(_C_DBL, double)
RCT_CASE(_C_BOOL, BOOL)
#define RCT_NULLABLE_CASE(_value, _type) \
case _value: { \
isNullableType = YES; \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \
}
RCT_NULLABLE_CASE(_C_SEL, SEL)
RCT_NULLABLE_CASE(_C_CHARPTR, const char *)
RCT_NULLABLE_CASE(_C_PTR, void *)
// Primitives
case _C_CHR: PRIMITIVE_CASE(char)
case _C_UCHR: PRIMITIVE_CASE(unsigned char)
case _C_SHT: PRIMITIVE_CASE(short)
case _C_USHT: PRIMITIVE_CASE(unsigned short)
case _C_INT: PRIMITIVE_CASE(int)
case _C_UINT: PRIMITIVE_CASE(unsigned int)
case _C_LNG: PRIMITIVE_CASE(long)
case _C_ULNG: PRIMITIVE_CASE(unsigned long)
case _C_LNG_LNG: PRIMITIVE_CASE(long long)
case _C_ULNG_LNG: PRIMITIVE_CASE(unsigned long long)
case _C_FLT: PRIMITIVE_CASE(float)
case _C_DBL: PRIMITIVE_CASE(double)
case _C_BOOL: PRIMITIVE_CASE(BOOL)
case _C_SEL: NULLABLE_PRIMITIVE_CASE(SEL)
case _C_CHARPTR: NULLABLE_PRIMITIVE_CASE(const char *)
case _C_PTR: NULLABLE_PRIMITIVE_CASE(void *)
case _C_ID: {
isNullableType = YES;
@ -269,12 +266,11 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
RCT_ARG_BLOCK(
id value = convert([RCTConvert class], selector, json);
CFBridgingRetain(value);
)
);
break;
}
case _C_STRUCT_B: {
NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector];
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = selector;
@ -295,7 +291,9 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
default: {
static const char *blockType = @encode(typeof(^{}));
if (!strcmp(objcType, blockType)) {
addBlockArgument();
BLOCK_CASE((NSArray *args), {
[bridge enqueueCallback:json args:args];
});
} else {
RCTLogError(@"Unsupported argument type '%@' in method %@.",
typeName, [self methodName]);
@ -303,124 +301,94 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
}
}
} else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
addBlockArgument();
BLOCK_CASE((NSArray *args), {
[bridge enqueueCallback:json args:args];
});
} else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
RCT_ARG_BLOCK(
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogArgumentError(weakSelf, index, json, "should be a function");
return NO;
}
__block BOOL didInvoke = NO;
RCT_BLOCK_ARGUMENT(^(NSError *error) {
if (RCTCheckCallbackMultipleInvocations(&didInvoke)) {
[bridge enqueueCallback:json args:@[RCTJSErrorFromNSError(error)]];
}
});
)
BLOCK_CASE((NSError *error), {
[bridge enqueueCallback:json args:@[RCTJSErrorFromNSError(error)]];
});
} else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
RCTAssert(i == numberOfArguments - 2,
@"The RCTPromiseResolveBlock must be the second to last parameter in %@",
[self methodName]);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function");
return NO;
}
__block BOOL didInvoke = NO;
RCT_BLOCK_ARGUMENT(^(id result) {
if (RCTCheckCallbackMultipleInvocations(&didInvoke)) {
[bridge enqueueCallback:json args:result ? @[result] : @[]];
}
});
)
BLOCK_CASE((id result), {
[bridge enqueueCallback:json args:result ? @[result] : @[]];
});
} else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
RCTAssert(i == numberOfArguments - 1,
@"The RCTPromiseRejectBlock must be the last parameter in %@",
[self methodName]);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function");
return NO;
}
__block BOOL didInvoke = NO;
RCT_BLOCK_ARGUMENT(^(NSString *code, NSString *message, NSError *error) {
if (RCTCheckCallbackMultipleInvocations(&didInvoke)) {
NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
[bridge enqueueCallback:json args:@[errorJSON]];
}
});
)
BLOCK_CASE((NSString *code, NSString *message, NSError *error), {
NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
[bridge enqueueCallback:json args:@[errorJSON]];
});
} else {
// Unknown argument type
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
" to support this type.", typeName, [self methodName]);
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert to support this type.",
typeName, [self methodName]);
}
if (RCT_DEBUG) {
RCTNullability nullability = argument.nullability;
if (!isNullableType) {
if (nullability == RCTNullable) {
RCTLogArgumentError(weakSelf, i - 2, typeName, "is marked as "
"nullable, but is not a nullable type.");
}
nullability = RCTNonnullable;
#if RCT_DEBUG
RCTNullability nullability = argument.nullability;
if (!isNullableType) {
if (nullability == RCTNullable) {
RCTLogArgumentError(weakSelf, i - 2, typeName, "is marked as "
"nullable, but is not a nullable type.");
}
nullability = RCTNonnullable;
}
/**
* Special case - Numbers are not nullable in Android, so we
* don't support this for now. In future we may allow it.
*/
if ([typeName isEqualToString:@"NSNumber"]) {
BOOL unspecified = (nullability == RCTNullabilityUnspecified);
if (!argument.unused && (nullability == RCTNullable || unspecified)) {
RCTLogArgumentError(weakSelf, i - 2, typeName,
[unspecified ? @"has unspecified nullability" : @"is marked as nullable"
stringByAppendingString: @" but React requires that all NSNumber "
"arguments are explicitly marked as `nonnull` to ensure "
"compatibility with Android."].UTF8String);
}
nullability = RCTNonnullable;
/**
* Special case - Numbers are not nullable in Android, so we
* don't support this for now. In future we may allow it.
*/
if ([typeName isEqualToString:@"NSNumber"]) {
BOOL unspecified = (nullability == RCTNullabilityUnspecified);
if (!argument.unused && (nullability == RCTNullable || unspecified)) {
RCTLogArgumentError(weakSelf, i - 2, typeName,
[unspecified ? @"has unspecified nullability" : @"is marked as nullable"
stringByAppendingString: @" but React requires that all NSNumber "
"arguments are explicitly marked as `nonnull` to ensure "
"compatibility with Android."].UTF8String);
}
nullability = RCTNonnullable;
}
if (nullability == RCTNonnullable) {
RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) {
if (json != nil) {
if (!oldBlock(bridge, index, json)) {
if (nullability == RCTNonnullable) {
RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) {
if (json != nil) {
if (!oldBlock(bridge, index, json)) {
return NO;
}
if (isNullableType) {
// Check converted value wasn't null either, as method probably
// won't gracefully handle a nil vallue for a nonull argument
void *value;
[invocation getArgument:&value atIndex:index + 2];
if (value == NULL) {
return NO;
}
if (isNullableType) {
// Check converted value wasn't null either, as method probably
// won't gracefully handle a nil vallue for a nonull argument
void *value;
[invocation getArgument:&value atIndex:index + 2];
if (value == NULL) {
return NO;
}
}
return YES;
}
RCTLogArgumentError(weakSelf, index, typeName, "must not be null");
return NO;
};
}
return YES;
}
RCTLogArgumentError(weakSelf, index, typeName, "must not be null");
return NO;
};
}
#endif
}
if (RCT_DEBUG) {
const char *objcType = _invocation.methodSignature.methodReturnType;
if (_methodInfo->isSync && objcType[0] != _C_ID)
RCTLogError(@"Return type of %@.%s should be (id) as the method is \"sync\"",
RCTBridgeModuleNameForClass(_moduleClass), self.JSMethodName);
#if RCT_DEBUG
const char *objcType = _invocation.methodSignature.methodReturnType;
if (_methodInfo->isSync && objcType[0] != _C_ID) {
RCTLogError(@"Return type of %@.%s should be (id) as the method is \"sync\"",
RCTBridgeModuleNameForClass(_moduleClass), self.JSMethodName);
}
#endif
_argumentBlocks = [argumentBlocks copy];
_argumentBlocks = argumentBlocks;
}
- (SEL)selector
@ -476,31 +444,31 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
[self processMethodSignature];
}
if (RCT_DEBUG) {
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", [self methodName], [module class]);
#if RCT_DEBUG
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", [self methodName], [module class]);
// Safety check
if (arguments.count != _argumentBlocks.count) {
NSInteger actualCount = arguments.count;
NSInteger expectedCount = _argumentBlocks.count;
// Safety check
if (arguments.count != _argumentBlocks.count) {
NSInteger actualCount = arguments.count;
NSInteger expectedCount = _argumentBlocks.count;
// Subtract the implicit Promise resolver and rejecter functions for implementations of async functions
if (self.functionType == RCTFunctionTypePromise) {
actualCount -= 2;
expectedCount -= 2;
}
RCTLogError(@"%@.%s was called with %zd arguments but expects %zd arguments. "
@"If you haven\'t changed this method yourself, this usually means that "
@"your versions of the native code and JavaScript code are out of sync. "
@"Updating both should make this error go away.",
RCTBridgeModuleNameForClass(_moduleClass), self.JSMethodName,
actualCount, expectedCount);
return nil;
// Subtract the implicit Promise resolver and rejecter functions for implementations of async functions
if (self.functionType == RCTFunctionTypePromise) {
actualCount -= 2;
expectedCount -= 2;
}
RCTLogError(@"%@.%s was called with %zd arguments but expects %zd arguments. "
@"If you haven\'t changed this method yourself, this usually means that "
@"your versions of the native code and JavaScript code are out of sync. "
@"Updating both should make this error go away.",
RCTBridgeModuleNameForClass(_moduleClass), self.JSMethodName,
actualCount, expectedCount);
return nil;
}
#endif
// Set arguments
NSUInteger index = 0;
@ -508,8 +476,7 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
RCTArgumentBlock block = _argumentBlocks[index];
if (!block(bridge, index, RCTNilIfNull(json))) {
// Invalid argument, abort
RCTLogArgumentError(self, index, json,
"could not be processed. Aborting method call.");
RCTLogArgumentError(self, index, json, "could not be processed. Aborting method call.");
return nil;
}
index++;
@ -537,19 +504,17 @@ SEL RCTParseMethodSignature(const char *input, NSArray<RCTMethodArgument *> **ar
}
}
id result = nil;
if (_methodInfo->isSync) {
void *pointer;
[_invocation getReturnValue:&pointer];
result = (__bridge id)pointer;
void *returnValue;
[_invocation getReturnValue:&returnValue];
return (__bridge id)returnValue;
}
return result;
return nil;
}
- (NSString *)methodName
{
if (_selector == NULL) {
if (!_selector) {
[self processMethodSignature];
}
return [NSString stringWithFormat:@"-[%@ %s]", _moduleClass, sel_getName(_selector)];