Break apart -[RCTComponentData propBlockForKey:inDictionary:]

Reviewed By: shergin

Differential Revision: D4578549

fbshipit-source-id: 726773fdf8c97cdcc3b0540b32278e36c00b19b5
This commit is contained in:
Pieter De Baets 2017-02-20 05:08:20 -08:00 committed by Facebook Github Bot
parent ebe3355de7
commit bdd27f4696
1 changed files with 221 additions and 233 deletions

View File

@ -19,31 +19,13 @@
#import "UIView+React.h" #import "UIView+React.h"
typedef void (^RCTPropBlock)(id<RCTComponent> view, id json); typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
typedef NSMutableDictionary<NSString *, RCTPropBlock> RCTPropBlockDictionary;
@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 @implementation RCTComponentData
{ {
id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
NSMutableDictionary<NSString *, RCTPropBlock> *_viewPropBlocks; RCTPropBlockDictionary *_viewPropBlocks;
NSMutableDictionary<NSString *, RCTPropBlock> *_shadowPropBlocks; RCTPropBlockDictionary *_shadowPropBlocks;
BOOL _implementsUIBlockToAmendWithShadowViewRegistry; BOOL _implementsUIBlockToAmendWithShadowViewRegistry;
__weak RCTBridge *_bridge; __weak RCTBridge *_bridge;
} }
@ -120,244 +102,250 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return shadowView; return shadowView;
} }
- (RCTPropBlock)propBlockForKey:(NSString *)name - (void)callCustomSetter:(SEL)setter onView:(id<RCTComponent>)view withProp:(id)json isShadowView:(BOOL)isShadowView
inDictionary:(NSMutableDictionary<NSString *, RCTPropBlock> *)propBlocks
{ {
BOOL shadowView = (propBlocks == _shadowPropBlocks); json = RCTNilIfNull(json);
RCTPropBlock propBlock = propBlocks[name]; if (!isShadowView) {
if (!propBlock) { if (!json && !_defaultView) {
// Only create default view if json is null
__weak RCTComponentData *weakSelf = self; _defaultView = [self createViewWithTag:nil];
// Get type
SEL type = NULL;
NSString *keyPath = nil;
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"propConfig%@_%@", shadowView ? @"Shadow" : @"", name]);
if ([_managerClass respondsToSelector:selector]) {
NSArray<NSString *> *typeAndKeyPath =
((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector);
type = RCTConvertSelectorForType(typeAndKeyPath[0]);
keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
} else {
propBlock = ^(__unused id view, __unused id json) {};
propBlocks[name] = propBlock;
return propBlock;
} }
((void (*)(id, SEL, id, id, id))objc_msgSend)(self.manager, setter, json, view, _defaultView);
} else {
((void (*)(id, SEL, id, id))objc_msgSend)(self.manager, setter, json, view);
}
}
// Check for custom setter static RCTPropBlock createEventSetter(NSString *propName, SEL setter, RCTBridge *bridge)
if ([keyPath isEqualToString:@"__custom__"]) { {
__weak RCTBridge *weakBridge = bridge;
// Get custom setter. There is no default view in the shadow case, so the selector is different. return ^(id target, id json) {
NSString *selectorString; void (^eventHandler)(NSDictionary *event) = nil;
if (!shadowView) { if ([RCTConvert BOOL:json]) {
selectorString = [NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, shadowView ? @"Shadow" : @""]; __weak id<RCTComponent> weakTarget = target;
} else { eventHandler = ^(NSDictionary *event) {
selectorString = [NSString stringWithFormat:@"set_%@:forShadowView:", name]; // The component no longer exists, we shouldn't send the event
} id<RCTComponent> strongTarget = weakTarget;
SEL customSetter = NSSelectorFromString(selectorString); if (!strongTarget) {
propBlock = ^(id<RCTComponent> view, id json) {
RCTComponentData *strongSelf = weakSelf;
if (!strongSelf) {
return; return;
} }
json = RCTNilIfNull(json);
if (!shadowView) {
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)(
strongSelf.manager, customSetter, json, view, strongSelf->_defaultView
);
} else {
((void (*)(id, SEL, id, id))objc_msgSend)(
strongSelf.manager, customSetter, json, view
);
}
};
} else { NSMutableDictionary *mutableEvent = [NSMutableDictionary dictionaryWithDictionary:event];
mutableEvent[@"target"] = strongTarget.reactTag;
// Disect keypath
NSString *key = name;
NSArray<NSString *> *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 json) = nil;
if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
// Special case for event handlers
__weak RCTViewManager *weakManager = self.manager;
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];
((NSMutableDictionary *)body)[@"target"] = weakTarget.reactTag;
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-declarations"
[weakManager.bridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(name) body:body]; [weakBridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(propName) body:mutableEvent];
#pragma clang diagnostic pop #pragma clang diagnostic pop
} : nil); };
}; }
((void (*)(id, SEL, id))objc_msgSend)(target, setter, eventHandler);
};
}
} else { static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, SEL type, SEL getter, SEL setter)
{
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
// Ordinary property handlers __block NSInvocation *targetInvocation = nil;
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type]; __block NSMutableData *defaultValue = nil;
if (!typeSignature) {
RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type));
return ^(__unused id<RCTComponent> view, __unused id json){};
}
switch (typeSignature.methodReturnType[0]) {
#define RCT_CASE(_value, _type) \ return ^(id target, id json) {
case _value: { \ if (!target) {
__block BOOL setDefaultValue = NO; \ return;
__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 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; \
}
RCT_CASE(_C_SEL, SEL) // Get default value
RCT_CASE(_C_CHARPTR, const char *) if (!defaultValue) {
RCT_CASE(_C_CHR, char) if (!json) {
RCT_CASE(_C_UCHR, unsigned char) // We only set the defaultValue when we first pass a non-null
RCT_CASE(_C_SHT, short) // value, so if the first value sent for a prop is null, it's
RCT_CASE(_C_USHT, unsigned short) // a no-op (we'd be resetting it to its default when its
RCT_CASE(_C_INT, int) // value is already the default).
RCT_CASE(_C_UINT, unsigned int) return;
RCT_CASE(_C_LNG, long) }
RCT_CASE(_C_ULNG, unsigned long) // Use NSMutableData to store defaultValue instead of malloc, so
RCT_CASE(_C_LNG_LNG, long long) // it will be freed automatically when setterBlock is released.
RCT_CASE(_C_ULNG_LNG, unsigned long long) defaultValue = [[NSMutableData alloc] initWithLength:typeSignature.methodReturnLength];
RCT_CASE(_C_FLT, float) if ([target respondsToSelector:getter]) {
RCT_CASE(_C_DBL, double) NSMethodSignature *signature = [target methodSignatureForSelector:getter];
RCT_CASE(_C_BOOL, BOOL) NSInvocation *sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
RCT_CASE(_C_PTR, void *) sourceInvocation.selector = getter;
RCT_CASE(_C_ID, id) [sourceInvocation invokeWithTarget:target];
[sourceInvocation getReturnValue:defaultValue.mutableBytes];
}
}
case _C_STRUCT_B: // Get value
default: { 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];
}
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature]; // Set value
typeInvocation.selector = type; if (!targetInvocation) {
typeInvocation.target = [RCTConvert class]; NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
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);
}
};
}
__block NSInvocation *targetInvocation = nil; - (RCTPropBlock)createPropBlock:(NSString *)name isShadowView:(BOOL)isShadowView
__block NSMutableData *defaultValue = nil; {
// Get type
SEL type = NULL;
NSString *keyPath = nil;
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"propConfig%@_%@", isShadowView ? @"Shadow" : @"", name]);
if ([_managerClass respondsToSelector:selector]) {
NSArray<NSString *> *typeAndKeyPath = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector);
type = RCTConvertSelectorForType(typeAndKeyPath[0]);
keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
} else {
return ^(__unused id view, __unused id json) {};
}
setterBlock = ^(id target, id json) { \ // Check for custom setter
if ([keyPath isEqualToString:@"__custom__"]) {
// Get custom setter. There is no default view in the shadow case, so the selector is different.
NSString *selectorString;
if (!isShadowView) {
selectorString = [NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, isShadowView ? @"Shadow" : @""];
} else {
selectorString = [NSString stringWithFormat:@"set_%@:forShadowView:", name];
}
if (!target) { SEL customSetter = NSSelectorFromString(selectorString);
return; __weak RCTComponentData *weakSelf = self;
} return ^(id<RCTComponent> view, id json) {
[weakSelf callCustomSetter:customSetter onView:view withProp:json isShadowView:isShadowView];
};
} else {
// Disect keypath
NSString *key = name;
NSArray<NSString *> *parts = [keyPath componentsSeparatedByString:@"."];
if (parts) {
key = parts.lastObject;
parts = [parts subarrayWithRange:(NSRange){0, parts.count - 1}];
}
// Get default value // Get property getter
if (!defaultValue) { SEL getter = NSSelectorFromString(key);
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 // Get property setter
BOOL freeValueOnCompletion = NO; SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
void *value = defaultValue.mutableBytes; [key substringToIndex:1].uppercaseString,
if (json) { [key substringFromIndex:1]]);
freeValueOnCompletion = YES;
value = malloc(typeSignature.methodReturnLength);
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
}
// Set value // Build setter block
if (!targetInvocation) { void (^setterBlock)(id target, id json) = nil;
NSMethodSignature *signature = [target methodSignatureForSelector:setter]; if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
targetInvocation = [NSInvocation invocationWithMethodSignature:signature]; type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
targetInvocation.selector = setter; // Special case for event handlers
} setterBlock = createEventSetter(name, setter, _bridge);
[targetInvocation setArgument:value atIndex:2]; } else {
[targetInvocation invokeWithTarget:target]; // Ordinary property handlers
if (freeValueOnCompletion) { NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
// Only free the value if we `malloc`d it locally, otherwise it if (!typeSignature) {
// points to `defaultValue.mutableBytes`, which is managed by ARC. RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type));
free(value); return ^(__unused id<RCTComponent> view, __unused id json){};
} }
}; switch (typeSignature.methodReturnType[0]) {
break;
} #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 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; \
}
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: {
setterBlock = createNSInvocationSetter(typeSignature, type, getter, setter);
break;
} }
} }
propBlock = ^(__unused id view, __unused id json) {
// Follow keypath
id target = view;
for (NSString *part in parts) {
target = [target valueForKey:part];
}
// Set property with json
setterBlock(target, RCTNilIfNull(json));
};
} }
if (RCT_DEBUG) { return ^(__unused id view, __unused id json) {
// Follow keypath
id target = view;
for (NSString *part in parts) {
target = [target valueForKey:part];
}
// Provide more useful log feedback if there's an error // Set property with json
RCTPropBlock unwrappedBlock = propBlock; setterBlock(target, RCTNilIfNull(json));
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); - (RCTPropBlock)propBlockForKey:(NSString *)name isShadowView:(BOOL)isShadowView
}; {
} RCTPropBlockDictionary *propBlocks = isShadowView ? _shadowPropBlocks : _viewPropBlocks;
RCTPropBlock propBlock = propBlocks[name];
if (!propBlock) {
propBlock = [self createPropBlock:name isShadowView:isShadowView];
#if RCT_DEBUG
// Provide more useful log feedback if there's an error
RCTPropBlock unwrappedBlock = propBlock;
__weak __typeof(self) weakSelf = self;
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);
};
#endif
propBlocks[name] = [propBlock copy]; propBlocks[name] = [propBlock copy];
} }
return propBlock; return propBlock;
@ -370,7 +358,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
} }
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) { [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key inDictionary:self->_viewPropBlocks](view, json); [self propBlockForKey:key isShadowView:NO](view, json);
}]; }];
if ([view respondsToSelector:@selector(didSetProps:)]) { if ([view respondsToSelector:@selector(didSetProps:)]) {
@ -385,7 +373,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
} }
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) { [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key inDictionary:self->_shadowPropBlocks](shadowView, json); [self propBlockForKey:key isShadowView:YES](shadowView, json);
}]; }];
if ([shadowView respondsToSelector:@selector(didSetProps:)]) { if ([shadowView respondsToSelector:@selector(didSetProps:)]) {