123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import "RCTComponentData.h"
- #import <objc/message.h>
- #import "RCTBridge.h"
- #import "RCTBridgeModule.h"
- #import "RCTComponentEvent.h"
- #import "RCTConvert.h"
- #import "RCTParserUtils.h"
- #import "RCTShadowView.h"
- #import "RCTUtils.h"
- #import "UIView+React.h"
- typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
- typedef NSMutableDictionary<NSString *, RCTPropBlock> RCTPropBlockDictionary;
- typedef void (^InterceptorBlock)(NSString *eventName, NSDictionary *event, id sender);
- /**
- * Get the converter function for the specified type
- */
- static SEL selectorForType(NSString *type)
- {
- const char *input = type.UTF8String;
- return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
- }
- @implementation RCTComponentData {
- id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
- RCTPropBlockDictionary *_viewPropBlocks;
- RCTPropBlockDictionary *_shadowPropBlocks;
- __weak RCTBridge *_bridge;
- }
- @synthesize manager = _manager;
- - (instancetype)initWithManagerClass:(Class)managerClass bridge:(RCTBridge *)bridge
- {
- if ((self = [super init])) {
- _bridge = bridge;
- _managerClass = managerClass;
- _viewPropBlocks = [NSMutableDictionary new];
- _shadowPropBlocks = [NSMutableDictionary new];
- _name = moduleNameForClass(managerClass);
- }
- return self;
- }
- - (RCTViewManager *)manager
- {
- if (!_manager) {
- _manager = [_bridge moduleForClass:_managerClass];
- }
- return _manager;
- }
- RCT_NOT_IMPLEMENTED(-(instancetype)init)
- - (UIView *)createViewWithTag:(NSNumber *)tag rootTag:(NSNumber *)rootTag
- {
- RCTAssertMainQueue();
- UIView *view = [self.manager view];
- view.reactTag = tag;
- view.rootTag = rootTag;
- #if !TARGET_OS_TV
- view.multipleTouchEnabled = YES;
- #endif
- view.userInteractionEnabled = YES; // required for touch handling
- view.layer.allowsGroupOpacity = YES; // required for touch handling
- return view;
- }
- - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
- {
- RCTShadowView *shadowView = [self.manager shadowView];
- shadowView.reactTag = tag;
- shadowView.viewName = _name;
- return shadowView;
- }
- - (void)callCustomSetter:(SEL)setter onView:(id<RCTComponent>)view withProp:(id)json isShadowView:(BOOL)isShadowView
- {
- json = RCTNilIfNull(json);
- if (!isShadowView) {
- if (!json && !_defaultView) {
- // Only create default view if json is null
- _defaultView = [self createViewWithTag:nil rootTag: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);
- }
- }
- static RCTPropBlock
- createEventSetter(NSString *propName, SEL setter, InterceptorBlock eventInterceptor, RCTBridge *bridge)
- {
- __weak RCTBridge *weakBridge = bridge;
- return ^(id target, id json) {
- void (^eventHandler)(NSDictionary *event) = nil;
- if ([RCTConvert BOOL:json]) {
- __weak id<RCTComponent> weakTarget = target;
- eventHandler = ^(NSDictionary *event) {
- // The component no longer exists, we shouldn't send the event
- id<RCTComponent> strongTarget = weakTarget;
- if (!strongTarget) {
- return;
- }
- if (eventInterceptor) {
- eventInterceptor(propName, event, strongTarget.reactTag);
- } else {
- RCTComponentEvent *componentEvent = [[RCTComponentEvent alloc] initWithName:propName
- viewTag:strongTarget.reactTag
- body:event];
- [weakBridge.eventDispatcher sendEvent:componentEvent];
- }
- };
- }
- ((void (*)(id, SEL, id))objc_msgSend)(target, setter, eventHandler);
- };
- }
- static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, SEL type, SEL getter, SEL setter)
- {
- NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
- typeInvocation.selector = type;
- typeInvocation.target = [RCTConvert class];
- __block NSInvocation *targetInvocation = nil;
- __block NSMutableData *defaultValue = nil;
- return ^(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
- BOOL freeValueOnCompletion = NO;
- void *value = defaultValue.mutableBytes;
- if (json) {
- freeValueOnCompletion = YES;
- value = malloc(typeSignature.methodReturnLength);
- if (!value) {
- // CWE - 391 : Unchecked error condition
- // https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
- // https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
- abort();
- }
- [typeInvocation setArgument:&json atIndex:2];
- [typeInvocation invoke];
- [typeInvocation getReturnValue:value];
- }
- // 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);
- }
- };
- }
- - (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<NSString *> *typeAndKeyPath = ((NSArray<NSString *> * (*)(id, SEL)) objc_msgSend)(_managerClass, selector);
- type = selectorForType(typeAndKeyPath[0]);
- keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
- } else {
- return ^(__unused id view, __unused 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];
- }
- SEL customSetter = NSSelectorFromString(selectorString);
- __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 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
- setterBlock = createEventSetter(name, setter, self.eventInterceptor, _bridge);
- } else {
- // Ordinary property handlers
- NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
- 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) \
- 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;
- }
- }
- }
- return ^(__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));
- };
- }
- }
- - (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];
- }
- return propBlock;
- }
- - (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent>)view
- {
- if (!view) {
- return;
- }
- [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
- [self propBlockForKey:key isShadowView:NO](view, json);
- }];
- }
- - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowView *)shadowView
- {
- if (!shadowView) {
- return;
- }
- [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
- [self propBlockForKey:key isShadowView:YES](shadowView, json);
- }];
- }
- - (NSDictionary<NSString *, id> *)viewConfig
- {
- NSMutableArray<NSString *> *bubblingEvents = [NSMutableArray new];
- NSMutableArray<NSString *> *directEvents = [NSMutableArray new];
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) {
- NSArray<NSString *> *events = [self.manager customBubblingEventTypes];
- for (NSString *event in events) {
- [bubblingEvents addObject:RCTNormalizeInputEventName(event)];
- }
- }
- #pragma clang diagnostic pop
- unsigned int count = 0;
- NSMutableDictionary *propTypes = [NSMutableDictionary new];
- Method *methods = class_copyMethodList(object_getClass(_managerClass), &count);
- for (unsigned int i = 0; i < count; i++) {
- SEL selector = method_getName(methods[i]);
- const char *selectorName = sel_getName(selector);
- if (strncmp(selectorName, "propConfig", strlen("propConfig")) != 0) {
- continue;
- }
- // We need to handle both propConfig_* and propConfigShadow_* methods
- const char *underscorePos = strchr(selectorName + strlen("propConfig"), '_');
- if (!underscorePos) {
- continue;
- }
- NSString *name = @(underscorePos + 1);
- NSString *type = ((NSArray<NSString *> * (*)(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);
- }
- if ([type isEqualToString:@"RCTBubblingEventBlock"]) {
- [bubblingEvents addObject:RCTNormalizeInputEventName(name)];
- propTypes[name] = @"BOOL";
- } else if ([type isEqualToString:@"RCTDirectEventBlock"]) {
- [directEvents addObject:RCTNormalizeInputEventName(name)];
- propTypes[name] = @"BOOL";
- } else {
- propTypes[name] = type;
- }
- }
- free(methods);
- #if RCT_DEBUG
- for (NSString *event in bubblingEvents) {
- if ([directEvents containsObject:event]) {
- RCTLogError(
- @"Component '%@' registered '%@' as both a bubbling event "
- "and a direct event",
- _name,
- event);
- }
- }
- #endif
- Class superClass = [_managerClass superclass];
- return @{
- @"propTypes" : propTypes,
- @"directEvents" : directEvents,
- @"bubblingEvents" : bubblingEvents,
- @"baseModuleName" : superClass == [NSObject class] ? (id)kCFNull : moduleNameForClass(superClass),
- };
- }
- static NSString *moduleNameForClass(Class managerClass)
- {
- // Hackety hack, this partially re-implements RCTBridgeModuleNameForClass
- // We want to get rid of RCT and RK prefixes, but a lot of JS code still references
- // view names by prefix. So, while RCTBridgeModuleNameForClass now drops these
- // prefixes by default, we'll still keep them around here.
- NSString *name = [managerClass moduleName];
- if (name.length == 0) {
- name = NSStringFromClass(managerClass);
- }
- if ([name hasPrefix:@"RK"]) {
- name = [name stringByReplacingCharactersInRange:(NSRange){0, @"RK".length} withString:@"RCT"];
- }
- if ([name hasSuffix:@"Manager"]) {
- name = [name substringToIndex:name.length - @"Manager".length];
- }
- RCTAssert(name.length, @"Invalid moduleName '%@'", name);
- return name;
- }
- @end
|