mirror of
https://github.com/status-im/react-native.git
synced 2025-01-11 01:56:26 +00:00
e1577df1fd
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
471 lines
16 KiB
Objective-C
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
|