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

@@ -7,19 +7,21 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <CoreGraphics/CoreGraphics.h>
/**
* Logical node in a tree of application components. Both `ShadowView`s and
* `UIView+React`s conform to this. Allows us to write utilities that
* reason about trees generally.
* Logical node in a tree of application components. Both `ShadowView` and
* `UIView` conforms to this. Allows us to write utilities that reason about
* trees generally.
*/
@protocol RCTViewNodeProtocol <NSObject>
@protocol RCTComponent <NSObject>
@property (nonatomic, copy) NSNumber *reactTag;
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex;
- (void)removeReactSubview:(id<RCTViewNodeProtocol>)subview;
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex;
- (void)removeReactSubview:(id<RCTComponent>)subview;
- (NSArray *)reactSubviews;
- (id<RCTViewNodeProtocol>)reactSuperview;
- (id<RCTComponent>)reactSuperview;
- (NSNumber *)reactTagAtPoint:(CGPoint)point;
// View/ShadowView is a root view

View File

@@ -0,0 +1,32 @@
/**
* 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 "RCTComponent.h"
#import "RCTDefines.h"
@class RCTShadowView;
@class RCTViewManager;
@interface RCTComponentData : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, strong, readonly) RCTViewManager *manager;
- (instancetype)initWithManager:(RCTViewManager *)manager NS_DESIGNATED_INITIALIZER;
- (id<RCTComponent>)createViewWithTag:(NSNumber *)tag;
- (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag;
- (void)setProps:(NSDictionary *)props forView:(id<RCTComponent>)view;
- (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowView *)shadowView;
- (NSDictionary *)viewConfig;
@end

View File

@@ -0,0 +1,321 @@
/**
* 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 "RCTComponentData.h"
#import <objc/message.h>
#import "RCTBridge.h"
#import "RCTShadowView.h"
#import "RCTViewManager.h"
typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
@interface RCTComponentProp : NSObject
@property (nonatomic, copy, readonly) NSString *type;
@property (nonatomic, copy) RCTPropBlock propBlock;
@end
@implementation RCTComponentProp
- (instancetype)initWithType:(NSString *)type
{
if ((self = [super init])) {
_type = [type copy];
}
return self;
}
@end
@implementation RCTComponentData
{
id<RCTComponent> _defaultView;
RCTShadowView *_defaultShadowView;
NSMutableDictionary *_viewPropBlocks;
NSMutableDictionary *_shadowPropBlocks;
}
- (instancetype)initWithManager:(RCTViewManager *)manager
{
if ((self = [super init])) {
_manager = manager;
_viewPropBlocks = [[NSMutableDictionary alloc] init];
_shadowPropBlocks = [[NSMutableDictionary alloc] init];
_name = RCTBridgeModuleNameForClass([manager class]);
RCTAssert(_name.length, @"Invalid moduleName '%@'", _name);
if ([_name hasSuffix:@"Manager"]) {
_name = [_name substringToIndex:_name.length - @"Manager".length];
}
}
return self;
}
RCT_NOT_IMPLEMENTED(-init)
- (id<RCTComponent>)createViewWithTag:(NSNumber *)tag
{
RCTAssertMainThread();
id<RCTComponent> view = (id<RCTComponent>)[_manager view];
view.reactTag = tag;
if ([view isKindOfClass:[UIView class]]) {
((UIView *)view).multipleTouchEnabled = YES;
((UIView *)view).userInteractionEnabled = YES; // required for touch handling
((UIView *)view).layer.allowsGroupOpacity = YES; // required for touch handling
}
return view;
}
- (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
{
RCTShadowView *shadowView = [_manager shadowView];
shadowView.reactTag = tag;
shadowView.viewName = _name;
return shadowView;
}
- (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
{
BOOL shadowView = [defaultView isKindOfClass:[RCTShadowView class]];
NSMutableDictionary *propBlocks = shadowView ? _shadowPropBlocks : _viewPropBlocks;
RCTPropBlock propBlock = propBlocks[name];
if (!propBlock) {
__weak RCTComponentData *weakSelf = self;
// Get type
SEL type = NULL;
NSString *keyPath = nil;
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"propConfig%@_%@", shadowView ? @"Shadow" : @"", name]);
Class managerClass = [_manager class];
if ([managerClass respondsToSelector:selector]) {
NSArray *typeAndKeyPath = ((NSArray *(*)(id, SEL))objc_msgSend)(managerClass, selector);
type = NSSelectorFromString([typeAndKeyPath[0] stringByAppendingString:@":"]);
keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
} else {
propBlock = ^(__unused id view, __unused id json) {};
propBlocks[name] = propBlock;
return propBlock;
}
// Check for custom setter
if ([keyPath isEqualToString:@"__custom__"]) {
// Get custom setter
SEL customSetter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, shadowView ? @"Shadow" : @""]);
propBlock = ^(id<RCTComponent> view, id json) {
((void (*)(id, SEL, id, id, id))objc_msgSend)(
weakSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, defaultView
);
};
} else {
// Disect keypath
NSString *key = name;
NSArray *parts = [keyPath componentsSeparatedByString:@"."];
if (parts) {
key = [parts lastObject];
parts = [parts subarrayWithRange:(NSRange){0, parts.count - 1}];
}
// Get property getter
SEL getter = NSSelectorFromString(key);
// Get property setter
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
// Build setter block
void (^setterBlock)(id target, id source, id json) = nil;
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
switch (typeSignature.methodReturnType[0]) {
#define RCT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
_type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
setterBlock = ^(id target, id source, id json) { \
set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
}; \
break; \
}
RCT_CASE(_C_SEL, SEL)
RCT_CASE(_C_CHARPTR, const char *)
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)
RCT_CASE(_C_PTR, void *)
RCT_CASE(_C_ID, id)
case _C_STRUCT_B:
default: {
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
[typeInvocation setSelector:type];
[typeInvocation setTarget:[RCTConvert class]];
__block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
setterBlock = ^(id target, id source, id json) { \
// Get value
void *value = malloc(typeSignature.methodReturnLength);
if (json) {
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
} else {
if (!sourceInvocation && source) {
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
[sourceInvocation setSelector:getter];
}
[sourceInvocation invokeWithTarget:source];
[sourceInvocation getReturnValue:value];
}
// Set value
if (!targetInvocation && target) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
[targetInvocation setSelector:setter];
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
free(value);
};
break;
}
}
propBlock = ^(__unused id view, __unused id json) {
// Follow keypath
id target = view;
for (NSString *part in parts) {
target = [target valueForKey:part];
}
if (json == (id)kCFNull) {
// Copy default property
id source = defaultView;
for (NSString *part in parts) {
source = [source valueForKey:part];
}
setterBlock(target, source, nil);
} else {
// Set property with json
setterBlock(target, nil, json);
}
};
}
if (RCT_DEBUG) {
// Provide more useful log feedback if there's an error
RCTPropBlock unwrappedBlock = propBlock;
propBlock = ^(id<RCTComponent> view, id json) {
NSString *logPrefix = [NSString stringWithFormat:
@"Error setting property '%@' of %@ with tag #%@: ",
name, weakSelf.name, view.reactTag];
RCTPerformBlockWithLogPrefix(^{ unwrappedBlock(view, json); }, logPrefix);
};
}
propBlocks[name] = [propBlock copy];
}
return propBlock;
}
- (void)setProps:(NSDictionary *)props forView:(id<RCTComponent>)view
{
if (!view) {
return;
}
if (!_defaultView) {
_defaultView = [self createViewWithTag:nil];
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key defaultView:_defaultView](view, json);
}];
}
- (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowView *)shadowView
{
if (!shadowView) {
return;
}
if (!_defaultShadowView) {
_defaultShadowView = [self createShadowViewWithTag:nil];
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key defaultView:_defaultShadowView](shadowView, json);
}];
[shadowView updateLayout];
}
- (NSDictionary *)viewConfig
{
Class managerClass = [_manager class];
NSMutableDictionary *propTypes = [[NSMutableDictionary alloc] init];
unsigned int count = 0;
Method *methods = class_copyMethodList(object_getClass(managerClass), &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
NSString *methodName = NSStringFromSelector(selector);
if ([methodName hasPrefix:@"propConfig"]) {
NSRange nameRange = [methodName rangeOfString:@"_"];
if (nameRange.length) {
NSString *name = [methodName substringFromIndex:nameRange.location + 1];
NSString *type = ((NSArray *(*)(id, SEL))objc_msgSend)(managerClass, selector)[0];
if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) {
RCTLogError(@"Property '%@' of component '%@' redefined from '%@' "
"to '%@'", name, _name, propTypes[name], type);
}
propTypes[name] = type;
}
}
}
free(methods);
return propTypes;
}
@end

View File

@@ -25,6 +25,7 @@
}
RCT_NOT_IMPLEMENTED(-initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(-initWithCoder:coder)
- (instancetype)initWithBridge:(RCTBridge *)bridge
{

View File

@@ -10,7 +10,7 @@
#import <UIKit/UIKit.h>
#import "Layout.h"
#import "RCTViewNodeProtocol.h"
#import "RCTComponent.h"
@class RCTSparseArray;
@@ -32,14 +32,14 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry);
* 3. If a node is "computed" and the constraint passed from above is identical to the constraint used to
* perform the last computation, we skip laying out the subtree entirely.
*/
@interface RCTShadowView : NSObject <RCTViewNodeProtocol>
@interface RCTShadowView : NSObject <RCTComponent>
@property (nonatomic, weak, readonly) RCTShadowView *superview;
@property (nonatomic, assign, readonly) css_node_t *cssNode;
@property (nonatomic, copy) NSString *viewName;
@property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
@property (nonatomic, assign) BOOL hasOnLayout;
@property (nonatomic, assign, getter=hasOnLayout) BOOL onLayout;
/**
* isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is
@@ -104,7 +104,7 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry);
@property (nonatomic, assign) css_justify_t justifyContent;
@property (nonatomic, assign) css_align_t alignSelf;
@property (nonatomic, assign) css_align_t alignItems;
@property (nonatomic, assign) css_position_type_t positionType;
@property (nonatomic, assign) css_position_type_t position;
@property (nonatomic, assign) css_wrap_type_t flexWrap;
@property (nonatomic, assign) CGFloat flex;

View File

@@ -541,7 +541,7 @@ RCT_STYLE_PROPERTY(FlexDirection, flexDirection, flex_direction, css_flex_direct
RCT_STYLE_PROPERTY(JustifyContent, justifyContent, justify_content, css_justify_t)
RCT_STYLE_PROPERTY(AlignSelf, alignSelf, align_self, css_align_t)
RCT_STYLE_PROPERTY(AlignItems, alignItems, align_items, css_align_t)
RCT_STYLE_PROPERTY(PositionType, positionType, position_type, css_position_type_t)
RCT_STYLE_PROPERTY(Position, position, position_type, css_position_type_t)
RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t)
- (void)setBackgroundColor:(UIColor *)color

View File

@@ -21,11 +21,6 @@
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry);
/**
* Underlying implementation of RCT_EXPORT_XXX macros. Ignore this.
*/
RCT_EXTERN void RCTSetViewProperty(NSString *, NSString *, SEL, id, id, id);
@interface RCTViewManager : NSObject <RCTBridgeModule>
/**
@@ -105,35 +100,28 @@ RCT_EXTERN void RCTSetViewProperty(NSString *, NSString *, SEL, id, id, id);
/**
* This handles the simple case, where JS and native property names match.
*/
#define RCT_EXPORT_VIEW_PROPERTY(name, type) RCT_REMAP_VIEW_PROPERTY(name, name, type)
#define RCT_EXPORT_SHADOW_PROPERTY(name, type) RCT_REMAP_SHADOW_PROPERTY(name, name, type)
#define RCT_EXPORT_VIEW_PROPERTY(name, type) \
+ (NSArray *)propConfig_##name { return @[@#type]; }
/**
* This macro maps a named property on the module to an arbitrary key path
* within the view or shadowView.
* This macro maps a named property to an arbitrary key path in the view.
*/
#define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \
RCT_CUSTOM_VIEW_PROPERTY(name, type, UIView) { \
RCTSetViewProperty(@#name, @#keyPath, @selector(type:), view, defaultView, json); \
}
#define RCT_REMAP_SHADOW_PROPERTY(name, keyPath, type) \
RCT_CUSTOM_SHADOW_PROPERTY(name, type, RCTShadowView) { \
RCTSetViewProperty(@#name, @#keyPath, @selector(type:), view, defaultView, json); \
}
#define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \
+ (NSArray *)propConfig_##name { return @[@#type, @#keyPath]; }
/**
* These macros can be used when you need to provide custom logic for setting
* This macro can be used when you need to provide custom logic for setting
* view properties. The macro should be followed by a method body, which can
* refer to "json", "view" and "defaultView" to implement the required logic.
*/
#define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \
+ (NSString *)getPropConfigView_##name { return @#type; } \
RCT_REMAP_VIEW_PROPERTY(name, __custom__, type) \
- (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView
#define RCT_CUSTOM_SHADOW_PROPERTY(name, type, viewClass) \
+ (NSString *)getPropConfigShadow_##name { return @#type; } \
- (void)set_##name:(id)json forShadowView:(viewClass *)view withDefaultView:(viewClass *)defaultView
/**
* This macro is used to map properties to the shadow view, instead of the view.
*/
#define RCT_EXPORT_SHADOW_PROPERTY(name, type) \
+ (NSArray *)propConfigShadow_##name { return @[@#type]; }
@end

View File

@@ -43,15 +43,6 @@ RCT_MULTI_ENUM_CONVERTER(UIAccessibilityTraits, (@{
@end
void RCTSetViewProperty(NSString *name, NSString *keyPath, SEL type,
id view, id defaultView, id json)
{
if ((json && !RCTSetProperty(view, keyPath, type, json)) ||
(!json && !RCTCopyProperty(view, defaultView, keyPath))) {
RCTLogError(@"%@ does not have setter for `%@` property", [view class], name);
}
}
@implementation RCTViewManager
@synthesize bridge = _bridge;
@@ -273,8 +264,8 @@ RCT_EXPORT_SHADOW_PROPERTY(flexWrap, css_wrap_type_t)
RCT_EXPORT_SHADOW_PROPERTY(justifyContent, css_justify_t)
RCT_EXPORT_SHADOW_PROPERTY(alignItems, css_align_t)
RCT_EXPORT_SHADOW_PROPERTY(alignSelf, css_align_t)
RCT_REMAP_SHADOW_PROPERTY(position, positionType, css_position_type_t)
RCT_EXPORT_SHADOW_PROPERTY(position, css_position_type_t)
RCT_REMAP_SHADOW_PROPERTY(onLayout, hasOnLayout, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(onLayout, BOOL)
@end

View File

@@ -9,11 +9,11 @@
#import <UIKit/UIKit.h>
#import "RCTViewNodeProtocol.h"
#import "RCTComponent.h"
//TODO: let's try to eliminate this category if possible
@interface UIView (React) <RCTViewNodeProtocol>
@interface UIView (React) <RCTComponent>
/**
* Used by the UIIManager to set the view frame.