Removed defaultViews

Summary:When a component prop is set to null/undefined, and doesn't have a default value specified in `getDefaultProps`, the null value is sent over the bridge as a sentinel to reset to the original native value.

On iOS this is handled by creating a default view instance for each view type. The default view is then used to look up the unmodified value for any prop that is reset.

This is rather expensive however, as it means that for complex views (e.g. WebView, MapView), a minimum of two instances will be created even if only one is needed, and the default view will remain even after all actual view instances have been released.

This diff replaces the default view mechanism with a system where the default value of each prop is recorded the first time it is set. This avoids the need to keep an extra copy of the whole view.

The only exception is for props that use the `RCT_CUSTOM_VIEW_PROPERTY` macro, which includes the default view as part of the interface. To avoid a breaking change, a default view will still be created for views that use this macro, but only if they are sent a null value (so very rarely, in practice). In a future update we may deprecate or replace `RCT_CUSTOM_VIEW_PROPERTY` if there are significant benefits to doing so.

Reviewed By: javache

Differential Revision: D3012115

fb-gh-sync-id: 259348e54aa8342f444ad182b6f883d2dd684973
shipit-source-id: 259348e54aa8342f444ad182b6f883d2dd684973
This commit is contained in:
Nick Lockwood 2016-03-09 08:55:45 -08:00 committed by Facebook Github Bot 4
parent 925e2eba32
commit 62177dbb3b
2 changed files with 98 additions and 88 deletions

View File

@ -39,8 +39,7 @@ typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
@implementation RCTComponentData
{
id<RCTComponent> _defaultView;
RCTShadowView *_defaultShadowView;
id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
NSMutableDictionary<NSString *, RCTPropBlock> *_viewPropBlocks;
NSMutableDictionary<NSString *, RCTPropBlock> *_shadowPropBlocks;
BOOL _implementsUIBlockToAmendWithShadowViewRegistry;
@ -105,10 +104,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return shadowView;
}
- (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
- (RCTPropBlock)propBlockForKey:(NSString *)name
inDictionary:(NSMutableDictionary<NSString *, RCTPropBlock> *)propBlocks
{
BOOL shadowView = [defaultView isKindOfClass:[RCTShadowView class]];
NSMutableDictionary<NSString *, RCTPropBlock> *propBlocks = shadowView ? _shadowPropBlocks : _viewPropBlocks;
BOOL shadowView = (propBlocks == _shadowPropBlocks);
RCTPropBlock propBlock = propBlocks[name];
if (!propBlock) {
@ -137,8 +136,16 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
SEL customSetter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, shadowView ? @"Shadow" : @""]);
propBlock = ^(id<RCTComponent> view, id json) {
RCTComponentData *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (!json && !strongSelf->_defaultView) {
// Only create default view if json is null
strongSelf->_defaultView = [strongSelf createViewWithTag:nil];
}
((void (*)(id, SEL, id, id, id))objc_msgSend)(
weakSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, defaultView
strongSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, strongSelf->_defaultView
);
};
@ -161,13 +168,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
[key substringFromIndex:1]]);
// Build setter block
void (^setterBlock)(id target, id source, id json) = nil;
void (^setterBlock)(id target, id json) = nil;
if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
// Special case for event handlers
__weak RCTViewManager *weakManager = _manager;
setterBlock = ^(id target, __unused id source, id json) {
setterBlock = ^(id target, id json) {
__weak id<RCTComponent> weakTarget = target;
((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) {
body = [NSMutableDictionary dictionaryWithDictionary:body];
@ -188,11 +195,23 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
#define RCT_CASE(_value, _type) \
case _value: { \
__block BOOL setDefaultValue = NO; \
__block _type defaultValue; \
_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)); \
setterBlock = ^(id target, id json) { \
if (json) { \
if (!setDefaultValue && target) { \
if ([target respondsToSelector:getter]) { \
defaultValue = get(target, getter); \
} \
setDefaultValue = YES; \
} \
set(target, setter, convert([RCTConvert class], type, json)); \
} else if (setDefaultValue) { \
set(target, setter, defaultValue); \
} \
}; \
break; \
}
@ -222,36 +241,60 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
__block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
__block NSMutableData *defaultValue = nil;
setterBlock = ^(id target, id source, id json) { \
setterBlock = ^(id target, id json) { \
if (!target) {
return;
}
// Get default value
if (!defaultValue) {
if (!json) {
// We only set the defaultValue when we first pass a non-null
// value, so if the first value sent for a prop is null, it's
// a no-op (we'd be resetting it to its default when its
// value is already the default).
return;
}
// Use NSMutableData to store defaultValue instead of malloc, so
// it will be freed automatically when setterBlock is released.
defaultValue = [[NSMutableData alloc] initWithLength:typeSignature.methodReturnLength];
if ([target respondsToSelector:getter]) {
NSMethodSignature *signature = [target methodSignatureForSelector:getter];
NSInvocation *sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
sourceInvocation.selector = getter;
[sourceInvocation invokeWithTarget:target];
[sourceInvocation getReturnValue:defaultValue.mutableBytes];
}
}
// Get value
void *value = malloc(typeSignature.methodReturnLength);
BOOL freeValueOnCompletion = NO;
void *value = defaultValue.mutableBytes;
if (json) {
freeValueOnCompletion = YES;
value = malloc(typeSignature.methodReturnLength);
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
} else {
if (!sourceInvocation && source) {
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
sourceInvocation.selector = getter;
}
[sourceInvocation invokeWithTarget:source];
[sourceInvocation getReturnValue:value];
}
// Set value
if (!targetInvocation && target) {
if (!targetInvocation) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
free(value);
if (freeValueOnCompletion) {
// Only free the value if we `malloc`d it locally, otherwise it
// points to `defaultValue.mutableBytes`, which is managed by ARC.
free(value);
}
};
break;
}
@ -266,20 +309,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
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);
}
// Set property with json
setterBlock(target, RCTNilIfNull(json));
};
}
@ -307,12 +338,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return;
}
if (!_defaultView) {
_defaultView = [self createViewWithTag:nil];
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key defaultView:_defaultView](view, json);
[self propBlockForKey:key inDictionary:_viewPropBlocks](view, json);
}];
if ([view respondsToSelector:@selector(didSetProps:)]) {
@ -326,12 +353,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return;
}
if (!_defaultShadowView) {
_defaultShadowView = [self createShadowViewWithTag:nil];
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key defaultView:_defaultShadowView](shadowView, json);
[self propBlockForKey:key inDictionary:_shadowPropBlocks](shadowView, json);
}];
if ([shadowView respondsToSelector:@selector(didSetProps:)]) {

View File

@ -853,49 +853,36 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
// setters here that will record the contentOffset beforehand, and
// restore it after the property has been set.
#define RCT_SET_AND_PRESERVE_OFFSET(setter, type) \
- (void)setter:(type)value \
{ \
CGPoint contentOffset = _scrollView.contentOffset; \
[_scrollView setter:value]; \
_scrollView.contentOffset = contentOffset; \
#define RCT_SET_AND_PRESERVE_OFFSET(setter, getter, type) \
- (void)setter:(type)value \
{ \
CGPoint contentOffset = _scrollView.contentOffset; \
[_scrollView setter:value]; \
_scrollView.contentOffset = contentOffset; \
} \
- (type)getter \
{ \
return [_scrollView getter]; \
}
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBounces, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, UIScrollViewIndicatorStyle)
RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, UIScrollViewKeyboardDismissMode)
RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, CGFloat);
RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
#pragma mark - Forward methods and properties to underlying UIScrollView
- (BOOL)respondsToSelector:(SEL)aSelector
{
return [super respondsToSelector:aSelector] || [_scrollView respondsToSelector:aSelector];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
[_scrollView setValue:value forKey:key];
}
- (id)valueForUndefinedKey:(NSString *)key
{
return [_scrollView valueForKey:key];
}
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, alwaysBounceHorizontal, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, alwaysBounceVertical, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBounces, bounces, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, bouncesZoom, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, canCancelContentTouches, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, decelerationRate, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, isDirectionalLockEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, indicatorStyle, UIScrollViewIndicatorStyle)
RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, keyboardDismissMode, UIScrollViewKeyboardDismissMode)
RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, maximumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, minimumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, isPagingEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, isScrollEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, scrollsToTop, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, showsHorizontalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat);
RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIEdgeInsets);
- (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
{