Refactor RCTUIManager

Summary:
Moved the view creation & property binding logic out of RCTUIManager into a separate RCTComponentData class - this follows the pattern used with the bridge.

I've also updated the property  binding to use pre-allocated blocks for setting the values, which is more efficient than the previous system that re-contructed the selectors each time it was called. This should improve view update performance significantly.
This commit is contained in:
Nick Lockwood
2015-08-06 15:44:15 -07:00
parent aefdf82cdc
commit deba13f698
21 changed files with 512 additions and 492 deletions

View File

@@ -54,13 +54,6 @@ RCT_EXTERN NSString *const RCTDidCreateNativeModules;
*/
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
*
*/
RCT_EXTERN void RCTRegisterModule(Class);
/**
* This function returns the module name for a given class.
*/
@@ -71,7 +64,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
*/
@interface RCTBridge : NSObject <RCTInvalidating>
/**
* Creates a new bridge with a custom RCTBridgeDelegate.
*

View File

@@ -48,6 +48,11 @@ NSArray *RCTGetModuleClasses(void)
return RCTModuleClasses;
}
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
*/
void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
@@ -57,7 +62,7 @@ void RCTRegisterModule(Class moduleClass)
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
NSStringFromClass(moduleClass));
moduleClass);
// Register module
[RCTModuleClasses addObject:moduleClass];

View File

@@ -94,7 +94,7 @@ extern dispatch_queue_t RCTJSThread;
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule([self class]); }
+ (void)load { RCTRegisterModule(self); }
/**
* Wrap the parameter line of your method implementation with this macro to

View File

@@ -136,21 +136,6 @@ typedef BOOL css_clip_t, css_backface_visibility_t;
@end
/**
* This function will attempt to set a property using a json value by first
* inferring the correct type from all available information, and then
* applying an appropriate conversion method. If the property does not
* exist, or the type cannot be inferred, the function will return NO.
*/
RCT_EXTERN BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
/**
* This function attempts to copy a property from the source object to the
* destination object using KVC. If the property does not exist, or cannot
* be set, it will do nothing and return NO.
*/
RCT_EXTERN BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
/**
* Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these.
*/

View File

@@ -1056,177 +1056,3 @@ RCT_ENUM_CONVERTER(RCTAnimationType, (@{
}), RCTAnimationTypeEaseInEaseOut, integerValue)
@end
BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json)
{
// Split keypath
NSArray *parts = [keyPath componentsSeparatedByString:@"."];
NSString *key = [parts lastObject];
for (NSUInteger i = 0; i < parts.count - 1; i++) {
target = [target valueForKey:parts[i]];
if (!target) {
return NO;
}
}
// Get property setter
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
// Fail early
if (![target respondsToSelector:setter]) {
return NO;
}
@try {
NSMethodSignature *signature = [RCTConvert methodSignatureForSelector:type];
switch (signature.methodReturnType[0]) {
#define RCT_SET_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
set(target, setter, convert([RCTConvert class], type, json)); \
break; \
}
RCT_SET_CASE(':', SEL)
RCT_SET_CASE('*', const char *)
RCT_SET_CASE('c', char)
RCT_SET_CASE('C', unsigned char)
RCT_SET_CASE('s', short)
RCT_SET_CASE('S', unsigned short)
RCT_SET_CASE('i', int)
RCT_SET_CASE('I', unsigned int)
RCT_SET_CASE('l', long)
RCT_SET_CASE('L', unsigned long)
RCT_SET_CASE('q', long long)
RCT_SET_CASE('Q', unsigned long long)
RCT_SET_CASE('f', float)
RCT_SET_CASE('d', double)
RCT_SET_CASE('B', BOOL)
RCT_SET_CASE('^', void *)
case '@': {
id (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend;
void (*set)(id, SEL, id) = (typeof(set))objc_msgSend;
set(target, setter, convert([RCTConvert class], type, json));
break;
}
case '{':
default: {
// Get converted value
void *value = malloc(signature.methodReturnLength);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:[RCTConvert class]];
[invocation setSelector:type];
[invocation setArgument:&json atIndex:2];
[invocation invoke];
[invocation getReturnValue:value];
// Set converted value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);
break;
}
}
return YES;
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while attempting to set property '%@' of \
'%@' with value '%@': %@", key, [target class], json, exception);
return NO;
}
}
BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
{
// Split keypath
NSArray *parts = [keyPath componentsSeparatedByString:@"."];
NSString *key = [parts lastObject];
for (NSUInteger i = 0; i < parts.count - 1; i++) {
source = [source valueForKey:parts[i]];
target = [target valueForKey:parts[i]];
if (!source || !target) {
return NO;
}
}
// Get property getter
SEL getter = NSSelectorFromString(key);
// Get property setter
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
// Fail early
if (![source respondsToSelector:getter] || ![target respondsToSelector:setter]) {
return NO;
}
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
switch (signature.methodReturnType[0]) {
#define RCT_COPY_CASE(_value, _type) \
case _value: { \
_type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
set(target, setter, get(source, getter)); \
break; \
}
RCT_COPY_CASE(':', SEL)
RCT_COPY_CASE('*', const char *)
RCT_COPY_CASE('c', char)
RCT_COPY_CASE('C', unsigned char)
RCT_COPY_CASE('s', short)
RCT_COPY_CASE('S', unsigned short)
RCT_COPY_CASE('i', int)
RCT_COPY_CASE('I', unsigned int)
RCT_COPY_CASE('l', long)
RCT_COPY_CASE('L', unsigned long)
RCT_COPY_CASE('q', long long)
RCT_COPY_CASE('Q', unsigned long long)
RCT_COPY_CASE('f', float)
RCT_COPY_CASE('d', double)
RCT_COPY_CASE('B', BOOL)
RCT_COPY_CASE('^', void *)
case '@': {
id (*get)(id, SEL) = (typeof(get))objc_msgSend;
void (*set)(id, SEL, id) = (typeof(set))objc_msgSend;
set(target, setter, get(source, getter));
break;
}
case '{':
default: {
// Get value
void *value = malloc(signature.methodReturnLength);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&getter atIndex:1];
[invocation invokeWithTarget:source];
[invocation getReturnValue:value];
// Set value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);
break;
}
}
return YES;
}

View File

@@ -17,7 +17,7 @@
#import "RCTLog.h"
#import "RCTUtils.h"
typedef void (^RCTArgumentBlock)(RCTBridge *, NSInvocation *, NSUInteger, id);
typedef void (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
@implementation RCTMethodArgument
@@ -47,7 +47,7 @@ typedef void (^RCTArgumentBlock)(RCTBridge *, NSInvocation *, NSUInteger, id);
{
Class _moduleClass;
SEL _selector;
NSMethodSignature *_methodSignature;
NSInvocation *_invocation;
NSArray *_argumentBlocks;
}
@@ -135,16 +135,20 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments)
methodName;
});
// Get method signature
_methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
RCTAssert(_methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName);
// Create method invocation
NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setSelector:_selector];
[invocation retainArguments];
_invocation = invocation;
// Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:(index) + 2]; \
}];
@@ -168,7 +172,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments)
};
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *objcType = [_methodSignature getArgumentTypeAtIndex:i];
const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
BOOL isNullableType = NO;
RCTMethodArgument *argument = arguments[i - 2];
NSString *typeName = argument.type;
@@ -176,45 +180,54 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments)
if ([RCTConvert respondsToSelector:selector]) {
switch (objcType[0]) {
#define RCT_CONVERT_CASE(_value, _type) \
case _value: { \
if (RCT_DEBUG && ([@#_type hasSuffix:@"*"] || [@#_type hasSuffix:@"Ref"] || [@#_type isEqualToString:@"id"])) { \
isNullableType = YES; \
} \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
break; \
}
#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_CONVERT_CASE(':', SEL)
RCT_CONVERT_CASE('*', const char *)
RCT_CONVERT_CASE('c', char)
RCT_CONVERT_CASE('C', unsigned char)
RCT_CONVERT_CASE('s', short)
RCT_CONVERT_CASE('S', unsigned short)
RCT_CONVERT_CASE('i', int)
RCT_CONVERT_CASE('I', unsigned int)
RCT_CONVERT_CASE('l', long)
RCT_CONVERT_CASE('L', unsigned long)
RCT_CONVERT_CASE('q', long long)
RCT_CONVERT_CASE('Q', unsigned long long)
RCT_CONVERT_CASE('f', float)
RCT_CONVERT_CASE('d', double)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
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)
case '{': {
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
#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; \
}
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
void *returnValue = malloc(methodSignature.methodReturnLength);
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[_invocation setTarget:[RCTConvert class]];
[_invocation setSelector:selector];
[_invocation setArgument:&json atIndex:2];
[_invocation invoke];
[_invocation getReturnValue:returnValue];
RCT_NULLABLE_CASE(_C_SEL, SEL)
RCT_NULLABLE_CASE(_C_CHARPTR, const char *)
RCT_NULLABLE_CASE(_C_PTR, void *)
RCT_NULLABLE_CASE(_C_ID, id)
case _C_STRUCT_B: {
NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector];
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
[typeInvocation setSelector:selector];
[typeInvocation setTarget:[RCTConvert class]];
[argumentBlocks addObject:
^(__unused RCTBridge *bridge, NSUInteger index, id json) {
void *returnValue = malloc(typeSignature.methodReturnLength);
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:returnValue];
[invocation setArgument:returnValue atIndex:index + 2];
@@ -323,13 +336,13 @@ case _value: { \
if (nullability == RCTNonnullable) {
RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) {
if (json == nil || json == (id)kCFNull) {
RCTLogArgumentError(weakSelf, index, typeName, "must not be null");
id null = nil;
[invocation setArgument:&null atIndex:index + 2];
} else {
oldBlock(bridge, invocation, index, json);
oldBlock(bridge, index, json);
}
};
}
@@ -370,22 +383,17 @@ case _value: { \
}
}
// Create invocation (we can't re-use this as it wouldn't be thread-safe)
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_methodSignature];
[invocation setArgument:&_selector atIndex:1];
[invocation retainArguments];
// Set arguments
NSUInteger index = 0;
for (id json in arguments) {
id arg = RCTNilIfNull(json);
RCTArgumentBlock block = _argumentBlocks[index];
block(bridge, invocation, index, arg);
block(bridge, index, arg);
index++;
}
// Invoke method
[invocation invokeWithTarget:module];
[_invocation invokeWithTarget:module];
}
- (NSString *)methodName

View File

@@ -244,7 +244,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
return self;
}
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
RCTPerformanceLoggerEnd(RCTPLTTI);