Skip to content

Commit

Permalink
Refactor RCTUIManager
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nicklockwood committed Aug 6, 2015
1 parent aefdf82 commit deba13f
Show file tree
Hide file tree
Showing 21 changed files with 514 additions and 494 deletions.
4 changes: 2 additions & 2 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ @implementation RCTSparseArrayTests

- (void)testDictionary
{
NSObject<RCTViewNodeProtocol> *myView = [[UIView alloc] init];
id<RCTComponent> myView = [[UIView alloc] init];
myView.reactTag = @4;

NSObject<RCTViewNodeProtocol> *myOtherView = [[UIView alloc] init];
id<RCTComponent> myOtherView = [[UIView alloc] init];
myOtherView.reactTag = @5;

RCTSparseArray *registry = [[RCTSparseArray alloc] init];
Expand Down
2 changes: 1 addition & 1 deletion Libraries/ART/RCTConvert+ART.m
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ + (ARTCGFloatArray)ARTCGFloatArray:(id)json
+ (ARTBrush *)ARTBrush:(id)json
{
NSArray *arr = [self NSArray:json];
NSUInteger type = [self NSUInteger:arr[0]];
NSUInteger type = [self NSUInteger:arr.firstObject];
switch (type) {
case 0: // solid color
// These are probably expensive allocations since it's often the same value.
Expand Down
8 changes: 0 additions & 8 deletions React/Base/RCTBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -71,7 +64,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
*/
@interface RCTBridge : NSObject <RCTInvalidating>


/**
* Creates a new bridge with a custom RCTBridgeDelegate.
*
Expand Down
7 changes: 6 additions & 1 deletion React/Base/RCTBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ @interface RCTBridge ()
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;
Expand All @@ -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];
Expand Down
2 changes: 1 addition & 1 deletion React/Base/RCTBridgeModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 0 additions & 15 deletions React/Base/RCTConvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
174 changes: 0 additions & 174 deletions React/Base/RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -1056,177 +1056,3 @@ + (NSPropertyList)NSPropertyList:(id)json
}), 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;
}
Loading

1 comment on commit deba13f

@qingfeng
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool

Please sign in to comment.