react-native/React/Views/RCTComponentData.m
Pieter De Baets e1577df1fd Move all header imports to "<React/..>"
Summary:
To make React Native play nicely with our internal build infrastructure we need to properly namespace all of our header includes.

Where previously you could do `#import "RCTBridge.h"`, you must now write this as `#import <React/RCTBridge.h>`. If your xcode project still has a custom header include path, both variants will likely continue to work, but for new projects, we're defaulting the header include path to `$(BUILT_PRODUCTS_DIR)/usr/local/include`, where the React and CSSLayout targets will copy a subset of headers too. To make Xcode copy headers phase work properly, you may need to add React as an explicit dependency to your app's scheme and disable "parallelize build".

Reviewed By: mmmulani

Differential Revision: D4213120

fbshipit-source-id: 84a32a4b250c27699e6795f43584f13d594a9a82
2016-11-23 07:58:39 -08:00

471 lines
16 KiB
Objective-C

/**
* 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 "RCTBridgeModule.h"
#import "RCTConvert.h"
#import "RCTShadowView.h"
#import "RCTUtils.h"
#import "UIView+React.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; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
NSMutableDictionary<NSString *, RCTPropBlock> *_viewPropBlocks;
NSMutableDictionary<NSString *, RCTPropBlock> *_shadowPropBlocks;
BOOL _implementsUIBlockToAmendWithShadowViewRegistry;
__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];
// 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);
_name = name;
_implementsUIBlockToAmendWithShadowViewRegistry = NO;
Class cls = _managerClass;
while (cls != [RCTViewManager class]) {
_implementsUIBlockToAmendWithShadowViewRegistry = _implementsUIBlockToAmendWithShadowViewRegistry ||
RCTClassOverridesInstanceMethod(cls, @selector(uiBlockToAmendWithShadowViewRegistry:));
cls = [cls superclass];
}
}
return self;
}
- (RCTViewManager *)manager
{
if (!_manager) {
_manager = [_bridge moduleForClass:_managerClass];
}
return _manager;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (UIView *)createViewWithTag:(NSNumber *)tag
{
RCTAssertMainQueue();
UIView *view = [self.manager view];
view.reactTag = tag;
#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;
}
- (RCTPropBlock)propBlockForKey:(NSString *)name
inDictionary:(NSMutableDictionary<NSString *, RCTPropBlock> *)propBlocks
{
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<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;
}
// 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<RCTComponent> view, id json) {
RCTComponentData *strongSelf = weakSelf;
if (!strongSelf) {
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<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 ignored "-Wdeprecated-declarations"
[weakManager.bridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(name) body:body];
#pragma clang diagnostic pop
} : nil);
};
} 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: {
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
__block NSInvocation *targetInvocation = nil;
__block NSMutableData *defaultValue = nil;
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
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];
}
// 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;
}
}
}
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) {
// 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<NSString *, id> *)props forView:(id<RCTComponent>)view
{
if (!view) {
return;
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key inDictionary:self->_viewPropBlocks](view, json);
}];
if ([view respondsToSelector:@selector(didSetProps:)]) {
[view didSetProps:[props allKeys]];
}
}
- (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowView *)shadowView
{
if (!shadowView) {
return;
}
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
[self propBlockForKey:key inDictionary:self->_shadowPropBlocks](shadowView, json);
}];
if ([shadowView respondsToSelector:@selector(didSetProps:)]) {
[shadowView didSetProps:[props allKeys]];
}
}
- (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
return @{
@"propTypes": propTypes,
@"directEvents": directEvents,
@"bubblingEvents": bubblingEvents,
};
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)registry
{
if (_implementsUIBlockToAmendWithShadowViewRegistry) {
return [[self manager] uiBlockToAmendWithShadowViewRegistry:registry];
}
return nil;
}
@end