diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index d7738b052..91c92f94a 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -19,31 +19,13 @@ #import "UIView+React.h" typedef void (^RCTPropBlock)(id 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 +typedef NSMutableDictionary RCTPropBlockDictionary; @implementation RCTComponentData { id _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY - NSMutableDictionary *_viewPropBlocks; - NSMutableDictionary *_shadowPropBlocks; + RCTPropBlockDictionary *_viewPropBlocks; + RCTPropBlockDictionary *_shadowPropBlocks; BOOL _implementsUIBlockToAmendWithShadowViewRegistry; __weak RCTBridge *_bridge; } @@ -120,244 +102,250 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) return shadowView; } -- (RCTPropBlock)propBlockForKey:(NSString *)name - inDictionary:(NSMutableDictionary *)propBlocks +- (void)callCustomSetter:(SEL)setter onView:(id)view withProp:(id)json isShadowView:(BOOL)isShadowView { - BOOL shadowView = (propBlocks == _shadowPropBlocks); - 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]); - if ([_managerClass respondsToSelector:selector]) { - NSArray *typeAndKeyPath = - ((NSArray *(*)(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; + json = RCTNilIfNull(json); + if (!isShadowView) { + if (!json && !_defaultView) { + // Only create default view if json is null + _defaultView = [self createViewWithTag:nil]; } + ((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 - if ([keyPath isEqualToString:@"__custom__"]) { - - // Get custom setter. There is no default view in the shadow case, so the selector is different. - NSString *selectorString; - if (!shadowView) { - selectorString = [NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, shadowView ? @"Shadow" : @""]; - } else { - selectorString = [NSString stringWithFormat:@"set_%@:forShadowView:", name]; - } - SEL customSetter = NSSelectorFromString(selectorString); - - propBlock = ^(id view, id json) { - RCTComponentData *strongSelf = weakSelf; - if (!strongSelf) { +static RCTPropBlock createEventSetter(NSString *propName, SEL setter, RCTBridge *bridge) +{ + __weak RCTBridge *weakBridge = bridge; + return ^(id target, id json) { + void (^eventHandler)(NSDictionary *event) = nil; + if ([RCTConvert BOOL:json]) { + __weak id weakTarget = target; + eventHandler = ^(NSDictionary *event) { + // The component no longer exists, we shouldn't send the event + id strongTarget = weakTarget; + if (!strongTarget) { 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 { - - // 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 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 weakTarget = target; - ((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) { - body = [NSMutableDictionary dictionaryWithDictionary:body]; - ((NSMutableDictionary *)body)[@"target"] = weakTarget.reactTag; + NSMutableDictionary *mutableEvent = [NSMutableDictionary dictionaryWithDictionary:event]; + mutableEvent[@"target"] = strongTarget.reactTag; #pragma clang diagnostic push #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 - } : 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 - NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type]; - if (!typeSignature) { - RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type)); - return ^(__unused id view, __unused id json){}; - } - switch (typeSignature.methodReturnType[0]) { + __block NSInvocation *targetInvocation = nil; + __block NSMutableData *defaultValue = nil; - #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; \ - } + return ^(id target, id json) { + if (!target) { + return; + } - 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) + // 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]; + } + } - case _C_STRUCT_B: - default: { + // Get value + 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]; - typeInvocation.selector = type; - typeInvocation.target = [RCTConvert class]; + // Set value + if (!targetInvocation) { + 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; - __block NSMutableData *defaultValue = nil; +- (RCTPropBlock)createPropBlock:(NSString *)name isShadowView:(BOOL)isShadowView +{ + // Get type + SEL type = NULL; + NSString *keyPath = nil; + SEL selector = NSSelectorFromString([NSString stringWithFormat:@"propConfig%@_%@", isShadowView ? @"Shadow" : @"", name]); + if ([_managerClass respondsToSelector:selector]) { + NSArray *typeAndKeyPath = ((NSArray *(*)(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) { - return; - } + SEL customSetter = NSSelectorFromString(selectorString); + __weak RCTComponentData *weakSelf = self; + return ^(id view, id json) { + [weakSelf callCustomSetter:customSetter onView:view withProp:json isShadowView:isShadowView]; + }; + } else { + // Disect keypath + NSString *key = name; + NSArray *parts = [keyPath componentsSeparatedByString:@"."]; + if (parts) { + key = parts.lastObject; + parts = [parts subarrayWithRange:(NSRange){0, parts.count - 1}]; + } - // 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 property getter + SEL getter = NSSelectorFromString(key); - // Get value - 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]; - } + // Get property setter + SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", + [key substringToIndex:1].uppercaseString, + [key substringFromIndex:1]]); - // Set value - if (!targetInvocation) { - 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); - } - }; - break; - } + // Build setter block + void (^setterBlock)(id target, id json) = nil; + if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") || + type == NSSelectorFromString(@"RCTDirectEventBlock:")) { + // Special case for event handlers + setterBlock = createEventSetter(name, setter, _bridge); + } else { + // Ordinary property handlers + NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type]; + if (!typeSignature) { + RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type)); + return ^(__unused id view, __unused id json){}; + } + switch (typeSignature.methodReturnType[0]) { + +#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 - RCTPropBlock unwrappedBlock = propBlock; - propBlock = ^(id view, id json) { - NSString *logPrefix = [NSString stringWithFormat: - @"Error setting property '%@' of %@ with tag #%@: ", - name, weakSelf.name, view.reactTag]; + // Set property with json + setterBlock(target, RCTNilIfNull(json)); + }; + } +} - 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 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]; } return propBlock; @@ -370,7 +358,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } [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:)]) { @@ -385,7 +373,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } [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:)]) {