Replaced RegExp method parser with recursive descent

Summary:
public

This diff replaces the RegEx module method parser with a handwritten recursive descent parser that's faster and easier to maintain.

The new parser is ~8 times faster when tested on the UIManager.managerChildren() method, and uses ~1/10 as much RAM.

The new parser also supports lightweight generics, and is more tolerant of white space.

(This means that you now can – and should – use types like `NSArray<NSString *> *` for your exported properties and method arguments, instead of `NSStringArray`).

Reviewed By: jspahrsummers

Differential Revision: D2736636

fb-gh-sync-id: f6a11431935fa8acc8ac36f3471032ec9a1c8490
This commit is contained in:
Nick Lockwood 2015-12-10 10:09:04 -08:00 committed by facebook-github-bot-4
parent ce7c0b735f
commit 88ac40666c
20 changed files with 413 additions and 170 deletions

View File

@ -132,7 +132,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a
{
__weak RCTModuleMethod *weakMethod;
@autoreleasepool {
__autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]];
__autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]];
weakMethod = method;
XCTAssertNotNil(method, @"RCTModuleMethod should have been created");
}

View File

@ -23,14 +23,14 @@
@implementation RCTMethodArgumentTests
extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes);
extern SEL RCTParseMethodSignature(NSString *methodSignature, NSArray **argTypes);
- (void)testOneArgument
{
NSArray *arguments;
NSString *methodName = @"foo:(NSInteger)foo";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:");
NSString *methodSignature = @"foo:(NSInteger)foo";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
XCTAssertEqual(arguments.count, (NSUInteger)1);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
}
@ -38,9 +38,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testTwoArguments
{
NSArray *arguments;
NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
NSString *methodSignature = @"foo:(NSInteger)foo bar:(BOOL)bar";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@ -49,9 +49,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testSpaces
{
NSArray *arguments;
NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
NSString *methodSignature = @"foo : (NSInteger)foo bar : (BOOL) bar";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@ -60,9 +60,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testNewlines
{
NSArray *arguments;
NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
NSString *methodSignature = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@ -71,9 +71,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testUnnamedArgs
{
NSArray *arguments;
NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo::");
NSString *methodSignature = @"foo:(NSInteger)foo:(BOOL)bar";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo::");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@ -82,9 +82,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testUntypedUnnamedArgs
{
NSArray *arguments;
NSString *methodName = @"foo:foo:bar:bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:::");
NSString *methodSignature = @"foo:foo:bar:bar";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:::");
XCTAssertEqual(arguments.count, (NSUInteger)3);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"id");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"id");
@ -94,9 +94,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testAttributes
{
NSArray *arguments;
NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
NSString *methodSignature = @"foo:(__attribute__((unused)) NSString *)foo bar:(__unused BOOL)bar";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@ -105,9 +105,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testNullability
{
NSArray *arguments;
NSString *methodName = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:baz:");
NSString *methodSignature = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:baz:");
XCTAssertEqual(arguments.count, (NSUInteger)3);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber");
@ -120,9 +120,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testSemicolonStripping
{
NSArray *arguments;
NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
NSString *methodSignature = @"foo:(NSString *)foo bar:(BOOL)bar;";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@ -131,9 +131,9 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
- (void)testUnused
{
NSArray *arguments;
NSString *methodName = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
NSString *methodSignature = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber");
@ -141,4 +141,44 @@ extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes
XCTAssertFalse(((RCTMethodArgument *)arguments[1]).unused);
}
- (void)testGenericArray
{
NSArray *arguments;
NSString *methodSignature = @"foo:(NSArray<NSString *> *)foo;";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
XCTAssertEqual(arguments.count, (NSUInteger)1);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArray");
}
- (void)testNestedGenericArray
{
NSArray *arguments;
NSString *methodSignature = @"foo:(NSArray<NSArray<NSString *> *> *)foo;";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
XCTAssertEqual(arguments.count, (NSUInteger)1);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArrayArray");
}
- (void)testGenericSet
{
NSArray *arguments;
NSString *methodSignature = @"foo:(NSSet<NSNumber *> *)foo;";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
XCTAssertEqual(arguments.count, (NSUInteger)1);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberSet");
}
- (void)testGenericDictionary
{
NSArray *arguments;
NSString *methodSignature = @"foo:(NSDictionary<NSString *, NSNumber *> *)foo;";
SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
XCTAssertEqual(arguments.count, (NSUInteger)1);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberDictionary");
}
@end

View File

@ -47,8 +47,8 @@ static BOOL RCTLogsError(void (^block)(void))
- (void)testNonnull
{
NSString *methodName = @"doFooWithBar:(nonnull NSString *)bar";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
NSString *methodSignature = @"doFooWithBar:(nonnull NSString *)bar";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
JSMethodName:nil
moduleClass:[self class]];
XCTAssertFalse(RCTLogsError(^{
@ -72,8 +72,8 @@ static BOOL RCTLogsError(void (^block)(void))
{
// Specifying an NSNumber param without nonnull isn't allowed
XCTAssertTrue(RCTLogsError(^{
NSString *methodName = @"doFooWithNumber:(NSNumber *)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
NSString *methodSignature = @"doFooWithNumber:(NSNumber *)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
JSMethodName:nil
moduleClass:[self class]];
// Invoke method to trigger parsing
@ -82,8 +82,8 @@ static BOOL RCTLogsError(void (^block)(void))
}
{
NSString *methodName = @"doFooWithNumber:(nonnull NSNumber *)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
NSString *methodSignature = @"doFooWithNumber:(nonnull NSNumber *)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
JSMethodName:nil
moduleClass:[self class]];
XCTAssertTrue(RCTLogsError(^{
@ -92,8 +92,8 @@ static BOOL RCTLogsError(void (^block)(void))
}
{
NSString *methodName = @"doFooWithDouble:(double)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
NSString *methodSignature = @"doFooWithDouble:(double)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
JSMethodName:nil
moduleClass:[self class]];
XCTAssertTrue(RCTLogsError(^{
@ -102,8 +102,8 @@ static BOOL RCTLogsError(void (^block)(void))
}
{
NSString *methodName = @"doFooWithInteger:(NSInteger)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
NSString *methodSignature = @"doFooWithInteger:(NSInteger)n";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
JSMethodName:nil
moduleClass:[self class]];
XCTAssertTrue(RCTLogsError(^{
@ -114,8 +114,8 @@ static BOOL RCTLogsError(void (^block)(void))
- (void)testStructArgument
{
NSString *methodName = @"doFooWithCGRect:(CGRect)s";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
NSString *methodSignature = @"doFooWithCGRect:(CGRect)s";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
JSMethodName:nil
moduleClass:[self class]];
@ -126,11 +126,11 @@ static BOOL RCTLogsError(void (^block)(void))
- (void)testWhitespaceTolerance
{
NSString *methodName = @"doFoo : \t (NSString *)foo";
NSString *methodSignature = @"doFoo : \t (NSString *)foo";
__block RCTModuleMethod *method;
XCTAssertFalse(RCTLogsError(^{
method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
JSMethodName:nil
moduleClass:[self class]];
}));

View File

@ -56,7 +56,7 @@ static NSString *RCTGenerateFormBoundary()
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
- (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData
- (RCTURLRequestCancellationBlock)process:(NSArray<NSDictionary *> *)formData
callback:(RCTHTTPQueryResult)callback
{
RCTAssertThread(_networker.methodQueue, @"process: must be called on method queue");
@ -289,7 +289,7 @@ RCT_EXPORT_MODULE()
}
};
}
NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
NSArray<NSDictionary *> *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
if (formData) {
RCTHTTPFormDataHelper *formDataHelper = [RCTHTTPFormDataHelper new];
formDataHelper.networker = self;

View File

@ -90,7 +90,7 @@ RCT_EXPORT_METHOD(setValues:(NSDictionary *)values)
/**
* Remove some values from the settings.
*/
RCT_EXPORT_METHOD(deleteValues:(NSStringArray *)keys)
RCT_EXPORT_METHOD(deleteValues:(NSArray<NSString *> *)keys)
{
_ignoringUpdates = YES;
for (NSString *key in keys) {

View File

@ -94,28 +94,13 @@ typedef NSURL RCTFileURL;
size:(id)size weight:(id)weight style:(id)style
scaleMultiplier:(CGFloat)scaleMultiplier;
typedef NSArray NSArrayArray;
+ (NSArray<NSArray *> *)NSArrayArray:(id)json;
typedef NSArray NSStringArray;
+ (NSArray<NSString *> *)NSStringArray:(id)json;
typedef NSArray NSStringArrayArray;
+ (NSArray<NSArray<NSString *> *> *)NSStringArrayArray:(id)json;
typedef NSArray NSDictionaryArray;
+ (NSArray<NSDictionary *> *)NSDictionaryArray:(id)json;
typedef NSArray NSURLArray;
+ (NSArray<NSURL *> *)NSURLArray:(id)json;
typedef NSArray RCTFileURLArray;
+ (NSArray<NSURL *> *)RCTFileURLArray:(id)json;
typedef NSArray NSNumberArray;
+ (NSArray<RCTFileURL *> *)RCTFileURLArray:(id)json;
+ (NSArray<NSNumber *> *)NSNumberArray:(id)json;
typedef NSArray UIColorArray;
+ (NSArray<UIColor *> *)UIColorArray:(id)json;
typedef NSArray CGColorArray;
@ -145,6 +130,18 @@ typedef BOOL css_clip_t, css_backface_visibility_t;
@interface RCTConvert (Deprecated)
/**
* Use lightweight generics syntax instead, e.g. NSArray<NSString *>
*/
typedef NSArray NSArrayArray __deprecated_msg("Use NSArray<NSArray *>");
typedef NSArray NSStringArray __deprecated_msg("Use NSArray<NSString *>");
typedef NSArray NSStringArrayArray __deprecated_msg("Use NSArray<NSArray<NSString *> *>");
typedef NSArray NSDictionaryArray __deprecated_msg("Use NSArray<NSDictionary *>");
typedef NSArray NSURLArray __deprecated_msg("Use NSArray<NSURL *>");
typedef NSArray RCTFileURLArray __deprecated_msg("Use NSArray<RCTFileURL *>");
typedef NSArray NSNumberArray __deprecated_msg("Use NSArray<NSNumber *>");
typedef NSArray UIColorArray __deprecated_msg("Use NSArray<UIColor *>");
/**
* Synchronous image loading is generally a bad idea for performance reasons.
* If you need to pass image references, try to use `RCTImageSource` and then
@ -162,6 +159,11 @@ RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber
RCT_EXTERN NSNumber *RCTConvertMultiEnumValue(const char *, NSDictionary *, NSNumber *, id);
RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id);
/**
* Get the converter function for the specified type
*/
RCT_EXTERN SEL RCTConvertSelectorForType(NSString *type);
/**
* This macro is used for logging conversion errors. This is just used to
* avoid repeating the same boilerplate for every error message.
@ -238,7 +240,7 @@ RCT_CUSTOM_CONVERTER(type, type, [RCT_DEBUG ? [self NSNumber:json] : json getter
* This macro is used for creating converter functions for typed arrays.
*/
#define RCT_ARRAY_CONVERTER(type) \
+ (NSArray<type *> *)type##Array:(id)json \
+ (NSArray<id> *)type##Array:(id)json \
{ \
return RCTConvertArrayValue(@selector(type:), json); \
}

View File

@ -13,6 +13,7 @@
#import "RCTDefines.h"
#import "RCTImageSource.h"
#import "RCTParserUtils.h"
#import "RCTUtils.h"
@implementation RCTConvert
@ -649,6 +650,12 @@ NSArray *RCTConvertArrayValue(SEL type, id json)
return values;
}
SEL RCTConvertSelectorForType(NSString *type)
{
const char *input = type.UTF8String;
return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
}
RCT_ARRAY_CONVERTER(NSURL)
RCT_ARRAY_CONVERTER(RCTFileURL)
RCT_ARRAY_CONVERTER(UIColor)

View File

@ -122,7 +122,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
NSArray<NSString *> *entries =
((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);
id<RCTBridgeMethod> moduleMethod =
[[RCTModuleMethod alloc] initWithObjCMethodName:entries[1]
[[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
JSMethodName:entries[0]
moduleClass:_moduleClass];

View File

@ -10,15 +10,10 @@
#import <Foundation/Foundation.h>
#import "RCTBridgeMethod.h"
#import "RCTNullability.h"
@class RCTBridge;
typedef NS_ENUM(NSUInteger, RCTNullability) {
RCTNullabilityUnspecified,
RCTNullable,
RCTNonnullable,
};
@interface RCTMethodArgument : NSObject
@property (nonatomic, copy, readonly) NSString *type;
@ -32,7 +27,7 @@ typedef NS_ENUM(NSUInteger, RCTNullability) {
@property (nonatomic, readonly) Class moduleClass;
@property (nonatomic, readonly) SEL selector;
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
- (instancetype)initWithMethodSignature:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName
moduleClass:(Class)moduleClass NS_DESIGNATED_INITIALIZER;

View File

@ -15,6 +15,7 @@
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTParserUtils.h"
#import "RCTUtils.h"
typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
@ -50,7 +51,7 @@ typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
Class _moduleClass;
NSInvocation *_invocation;
NSArray<RCTArgumentBlock> *_argumentBlocks;
NSString *_objCMethodName;
NSString *_methodSignature;
SEL _selector;
NSDictionary *_profileArgs;
}
@ -68,77 +69,106 @@ static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index,
RCT_NOT_IMPLEMENTED(- (instancetype)init)
void RCTParseObjCMethodName(NSString **, NSArray<RCTMethodArgument *> **);
void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument *> **arguments)
// returns YES if the selector ends in a colon (indicating that there is at
// least one argument, and maybe more selector parts) or NO if it doesn't.
static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector)
{
static NSRegularExpression *typeNameRegex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))";
NSString *constPattern = @"(?:const)";
NSString *nullablePattern = @"(?:__nullable|nullable|__attribute__\\(\\(nullable\\)\\))";
NSString *nonnullPattern = @"(?:__nonnull|nonnull|__attribute__\\(\\(nonnull\\)\\))";
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:(%@)|%@|(%@)|(%@))\\s*)",
unusedPattern, constPattern, nullablePattern, nonnullPattern];
NSString *pattern = [NSString stringWithFormat:@"(?<=:)(\\s*\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\))?\\s*\\w+",
annotationPattern];
typeNameRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
});
NSString *selectorPart;
if (RCTParseIdentifier(input, &selectorPart)) {
[selector appendString:selectorPart];
}
RCTSkipWhitespace(input);
if (RCTReadChar(input, ':')) {
[selector appendString:@":"];
RCTSkipWhitespace(input);
return YES;
}
return NO;
}
// Extract argument types
NSString *methodName = *objCMethodName;
NSRange methodRange = {0, methodName.length};
NSMutableArray *args = [NSMutableArray array];
[typeNameRegex enumerateMatchesInString:methodName options:0 range:methodRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
NSRange typeRange = [result rangeAtIndex:5];
NSString *type = typeRange.length ? [methodName substringWithRange:typeRange] : @"id";
BOOL unused = ([result rangeAtIndex:2].length > 0);
RCTNullability nullability = [result rangeAtIndex:3].length ? RCTNullable :
[result rangeAtIndex:4].length ? RCTNonnullable : RCTNullabilityUnspecified;
static BOOL RCTParseUnused(const char **input)
{
return RCTReadString(input, "__unused") ||
RCTReadString(input, "__attribute__((unused))");
}
static RCTNullability RCTParseNullability(const char **input)
{
if (RCTReadString(input, "nullable")) {
return RCTNullable;
} else if (RCTReadString(input, "nonnull")) {
return RCTNonnullable;
}
return RCTNullabilityUnspecified;
}
SEL RCTParseMethodSignature(NSString *, NSArray<RCTMethodArgument *> **);
SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument *> **arguments)
{
const char *input = methodSignature.UTF8String;
RCTSkipWhitespace(&input);
NSMutableArray *args;
NSMutableString *selector = [NSMutableString new];
while (RCTParseSelectorPart(&input, selector)) {
if (!args) {
args = [NSMutableArray new];
}
// Parse type
if (RCTReadChar(&input, '(')) {
RCTSkipWhitespace(&input);
BOOL unused = RCTParseUnused(&input);
RCTSkipWhitespace(&input);
RCTNullability nullability = RCTParseNullability(&input);
RCTSkipWhitespace(&input);
NSString *type = RCTParseType(&input);
[args addObject:[[RCTMethodArgument alloc] initWithType:type
nullability:nullability
unused:unused]];
}];
RCTSkipWhitespace(&input);
RCTReadChar(&input, ')');
RCTSkipWhitespace(&input);
} else {
// Type defaults to id if unspecified
[args addObject:[[RCTMethodArgument alloc] initWithType:@"id"
nullability:RCTNullable
unused:NO]];
}
// Argument name
RCTParseIdentifier(&input, NULL);
RCTSkipWhitespace(&input);
}
*arguments = [args copy];
// Remove the parameter types and names
methodName = [typeNameRegex stringByReplacingMatchesInString:methodName options:0
range:methodRange
withTemplate:@""];
// Remove whitespace
methodName = [methodName stringByReplacingOccurrencesOfString:@"\n" withString:@""];
methodName = [methodName stringByReplacingOccurrencesOfString:@" " withString:@""];
// Strip trailing semicolon
if ([methodName hasSuffix:@";"]) {
methodName = [methodName substringToIndex:methodName.length - 1];
return NSSelectorFromString(selector);
}
*objCMethodName = methodName;
}
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
- (instancetype)initWithMethodSignature:(NSString *)methodSignature
JSMethodName:(NSString *)JSMethodName
moduleClass:(Class)moduleClass
{
if ((self = [super init])) {
_moduleClass = moduleClass;
_objCMethodName = [objCMethodName copy];
_methodSignature = [methodSignature copy];
_JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({
NSString *methodName = objCMethodName;
NSString *methodName = methodSignature;
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.location != NSNotFound) {
methodName = [methodName substringToIndex:colonRange.location];
}
methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
RCTAssert(methodName.length, @"%@ is not a valid JS function name, please"
" supply an alternative using RCT_REMAP_METHOD()", objCMethodName);
" supply an alternative using RCT_REMAP_METHOD()", methodSignature);
methodName;
});
if ([_objCMethodName rangeOfString:@"RCTPromise"].length) {
if ([_methodSignature rangeOfString:@"RCTPromise"].length) {
_functionType = RCTFunctionTypePromise;
} else {
_functionType = RCTFunctionTypeNormal;
@ -151,15 +181,12 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
- (void)processMethodSignature
{
NSArray<RCTMethodArgument *> *arguments;
NSString *objCMethodName = _objCMethodName;
RCTParseObjCMethodName(&objCMethodName, &arguments);
_selector = NSSelectorFromString(objCMethodName);
RCTAssert(_selector, @"%@ is not a valid selector", objCMethodName);
_selector = RCTParseMethodSignature(_methodSignature, &arguments);
RCTAssert(_selector, @"%@ is not a valid selector", _methodSignature);
// Create method invocation
NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName);
RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", _methodSignature);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = _selector;
_invocation = invocation;
@ -203,7 +230,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
BOOL isNullableType = NO;
RCTMethodArgument *argument = arguments[i - 2];
NSString *typeName = argument.type;
SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]);
SEL selector = RCTConvertSelectorForType(typeName);
if ([RCTConvert respondsToSelector:selector]) {
switch (objcType[0]) {
@ -296,7 +323,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
} else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
RCTAssert(i == numberOfArguments - 2,
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
_moduleClass, objCMethodName);
_moduleClass, _methodSignature);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function");
@ -310,7 +337,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument
} else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
RCTAssert(i == numberOfArguments - 1,
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
_moduleClass, objCMethodName);
_moduleClass, _methodSignature);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function");

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, RCTNullability) {
RCTNullabilityUnspecified,
RCTNullable,
RCTNonnullable,
};

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "RCTDefines.h"
@interface RCTParserUtils : NSObject
/**
* Generic utility functions for parsing Objective-C source code.
*/
RCT_EXTERN BOOL RCTReadChar(const char **input, char c);
RCT_EXTERN BOOL RCTReadString(const char **input, const char *string);
RCT_EXTERN void RCTSkipWhitespace(const char **input);
RCT_EXTERN BOOL RCTParseIdentifier(const char **input, NSString **string);
/**
* Parse an Objective-C type into a form that can be used by RCTConvert.
* This doesn't really belong here, but it's used by both RCTConvert and
* RCTModuleMethod, which makes it difficult to find a better home for it.
*/
RCT_EXTERN NSString *RCTParseType(const char **input);
@end

118
React/Base/RCTParserUtils.m Normal file
View File

@ -0,0 +1,118 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTParserUtils.h"
#import "RCTLog.h"
@implementation RCTParserUtils
BOOL RCTReadChar(const char **input, char c)
{
if (**input == c) {
(*input)++;
return YES;
}
return NO;
}
BOOL RCTReadString(const char **input, const char *string)
{
int i;
for (i = 0; string[i] != 0; i++) {
if (string[i] != (*input)[i]) {
return NO;
}
}
*input += i;
return YES;
}
void RCTSkipWhitespace(const char **input)
{
while (isspace(**input)) {
(*input)++;
}
}
static BOOL RCTIsIdentifierHead(const char c)
{
return isalpha(c) || c == '_';
}
static BOOL RCTIsIdentifierTail(const char c)
{
return isalnum(c) || c == '_';
}
BOOL RCTParseIdentifier(const char **input, NSString **string)
{
const char *start = *input;
if (!RCTIsIdentifierHead(**input)) {
return NO;
}
(*input)++;
while (RCTIsIdentifierTail(**input)) {
(*input)++;
}
if (string) {
*string = [[NSString alloc] initWithBytes:start
length:(NSInteger)(*input - start)
encoding:NSASCIIStringEncoding];
}
return YES;
}
static BOOL RCTIsCollectionType(NSString *type)
{
static NSSet *collectionTypes;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
collectionTypes = [[NSSet alloc] initWithObjects:
@"NSArray", @"NSSet", @"NSDictionary", nil];
});
return [collectionTypes containsObject:type];
}
NSString *RCTParseType(const char **input)
{
NSString *type;
RCTParseIdentifier(input, &type);
RCTSkipWhitespace(input);
if (RCTReadChar(input, '<')) {
RCTSkipWhitespace(input);
NSString *subtype = RCTParseType(input);
if (RCTIsCollectionType(type)) {
if ([type isEqualToString:@"NSDictionary"]) {
// Dictionaries have both a key *and* value type, but the key type has
// to be a string for JSON, so we only care about the value type
if (RCT_DEBUG && ![subtype isEqualToString:@"NSString"]) {
RCTLogError(@"%@ is not a valid key type for a JSON dictionary", subtype);
}
RCTSkipWhitespace(input);
RCTReadChar(input, ',');
RCTSkipWhitespace(input);
subtype = RCTParseType(input);
}
if (![subtype isEqualToString:@"id"]) {
type = [type stringByReplacingCharactersInRange:(NSRange){0, 2 /* "NS" */}
withString:subtype];
}
} else {
// It's a protocol rather than a generic collection - ignore it
}
RCTSkipWhitespace(input);
RCTReadChar(input, '>');
}
RCTSkipWhitespace(input);
RCTReadChar(input, '*');
return type;
}
@end

View File

@ -319,7 +319,7 @@ RCT_EXPORT_MODULE()
#pragma mark - Exported JS Functions
RCT_EXPORT_METHOD(multiGet:(NSStringArray *)keys
RCT_EXPORT_METHOD(multiGet:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback)
{
NSDictionary *errorOut = [self _ensureSetup];
@ -338,7 +338,7 @@ RCT_EXPORT_METHOD(multiGet:(NSStringArray *)keys
callback(@[RCTNullIfNil(errors), result]);
}
RCT_EXPORT_METHOD(multiSet:(NSStringArrayArray *)kvPairs
RCT_EXPORT_METHOD(multiSet:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback)
{
NSDictionary *errorOut = [self _ensureSetup];
@ -358,7 +358,7 @@ RCT_EXPORT_METHOD(multiSet:(NSStringArrayArray *)kvPairs
callback(@[RCTNullIfNil(errors)]);
}
RCT_EXPORT_METHOD(multiMerge:(NSStringArrayArray *)kvPairs
RCT_EXPORT_METHOD(multiMerge:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback)
{
NSDictionary *errorOut = [self _ensureSetup];
@ -394,7 +394,7 @@ RCT_EXPORT_METHOD(multiMerge:(NSStringArrayArray *)kvPairs
callback(@[RCTNullIfNil(errors)]);
}
RCT_EXPORT_METHOD(multiRemove:(NSStringArray *)keys
RCT_EXPORT_METHOD(multiRemove:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback)
{
NSDictionary *errorOut = [self _ensureSetup];

View File

@ -34,7 +34,7 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_METHOD(reportSoftException:(NSString *)message
stack:(NSDictionaryArray *)stack
stack:(NSArray<NSDictionary *> *)stack
exceptionId:(nonnull NSNumber *)exceptionId)
{
[_bridge.redBox showErrorMessage:message withStack:stack];
@ -45,7 +45,7 @@ RCT_EXPORT_METHOD(reportSoftException:(NSString *)message
}
RCT_EXPORT_METHOD(reportFatalException:(NSString *)message
stack:(NSDictionaryArray *)stack
stack:(NSArray<NSDictionary *> *)stack
exceptionId:(nonnull NSNumber *)exceptionId)
{
[_bridge.redBox showErrorMessage:message withStack:stack];
@ -66,7 +66,7 @@ RCT_EXPORT_METHOD(reportFatalException:(NSString *)message
}
RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message
stack:(NSDictionaryArray *)stack
stack:(NSArray<NSDictionary *> *)stack
exceptionId:(nonnull NSNumber *)exceptionId)
{
[_bridge.redBox updateErrorMessage:message withStack:stack];
@ -78,7 +78,7 @@ RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message
// Deprecated. Use reportFatalException directly instead.
RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
stack:(NSDictionaryArray *)stack)
stack:(NSArray<NSDictionary *> *)stack)
{
[self reportFatalException:message stack:stack exceptionId:@-1];
}

View File

@ -721,11 +721,11 @@ RCT_EXPORT_METHOD(replaceExistingNonRootView:(nonnull NSNumber *)reactTag
}
RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
moveFromIndices:(NSNumberArray *)moveFromIndices
moveToIndices:(NSNumberArray *)moveToIndices
addChildReactTags:(NSNumberArray *)addChildReactTags
addAtIndices:(NSNumberArray *)addAtIndices
removeAtIndices:(NSNumberArray *)removeAtIndices)
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices)
{
[self _manageChildren:containerReactTag
moveFromIndices:moveFromIndices

View File

@ -24,6 +24,7 @@
13A0C2891B74F71200B29F6F /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */; };
13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */; };
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; };
13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */; };
13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AB90C01B6FA36700713B4F /* RCTComponentData.m */; };
13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; };
13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */; };
@ -141,6 +142,9 @@
13A0C2881B74F71200B29F6F /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; sourceTree = "<group>"; };
13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = "<group>"; };
13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = "<group>"; };
13A6E20C1C19AA0C00845B82 /* RCTParserUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTParserUtils.h; sourceTree = "<group>"; };
13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTParserUtils.m; sourceTree = "<group>"; };
13A6E20F1C19ABC700845B82 /* RCTNullability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNullability.h; sourceTree = "<group>"; };
13AB90BF1B6FA36700713B4F /* RCTComponentData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponentData.h; sourceTree = "<group>"; };
13AB90C01B6FA36700713B4F /* RCTComponentData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTComponentData.m; sourceTree = "<group>"; };
13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = "<group>"; };
@ -518,6 +522,9 @@
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */,
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */,
13A6E20F1C19ABC700845B82 /* RCTNullability.h */,
13A6E20C1C19AA0C00845B82 /* RCTParserUtils.h */,
13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */,
142014181B32094000CC17BA /* RCTPerformanceLogger.h */,
142014171B32094000CC17BA /* RCTPerformanceLogger.m */,
830A229C1A66C68A008503DA /* RCTRootView.h */,
@ -674,6 +681,7 @@
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */,
13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */,
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */,
13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */,
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */,

View File

@ -114,7 +114,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
if ([managerClass respondsToSelector:selector]) {
NSArray<NSString *> *typeAndKeyPath =
((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(managerClass, selector);
type = NSSelectorFromString([typeAndKeyPath[0] stringByAppendingString:@":"]);
type = RCTConvertSelectorForType(typeAndKeyPath[0]);
keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
} else {
propBlock = ^(__unused id view, __unused id json) {};

View File

@ -10,7 +10,6 @@
#import "RCTPickerManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTPicker.h"
@implementation RCTPickerManager
@ -22,7 +21,7 @@ RCT_EXPORT_MODULE()
return [RCTPicker new];
}
RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)
RCT_EXPORT_VIEW_PROPERTY(items, NSArray<NSDictionary *>)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)

View File

@ -22,7 +22,7 @@ RCT_EXPORT_MODULE()
return [RCTSegmentedControl new];
}
RCT_EXPORT_VIEW_PROPERTY(values, NSStringArray)
RCT_EXPORT_VIEW_PROPERTY(values, NSArray<NSString *>)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(momentary, BOOL)