Files
react-native/React/Views/RCTComponentData.m
T
simek 2160377574 remove most of tvOS remnants from the code (#29407)
Summary:
Refs: [0.62 release](https://reactnative.dev/blog/#moving-apple-tv-to-react-native-tvos), https://github.com/facebook/react-native/issues/28706, https://github.com/facebook/react-native/issues/28743, https://github.com/facebook/react-native/issues/29018

This PR removes most of the tvOS remnants in the code. Most of the changes are related to the tvOS platform removal from `.podspec` files, tvOS specific conditionals removal (Obj-C + JS) or tvOS CI/testing pipeline related code.

In addition to the changes listed above I have removed the deprecated `Platform.isTVOS` method. I'm not sure how `Platform.isTV` method is correlated with Android TV devices support which is technically not deprecated in the core so I left this method untouched for now.

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

* **[Internal] [Removed]** - remove most of tvOS remnants from the code:
  * `TVEventHandler`, `TVTouchable`, `RCTTVView`, `RCTTVRemoteHandler` and `RCTTVNavigationEventEmitter`
* **[Internal] [Removed]** - remove `TARGET_TV_OS` flag and all the usages
* **[iOS] [Removed]** - remove deprecated `Platform.isTVOS` method
* **[iOS] [Removed]** - remove deprecated and TV related props from View:
  * `isTVSelectable`, `hasTVPreferredFocus` and `tvParallaxProperties`
* **[iOS] [Removed]** - remove `BackHandler` utility implementation

Pull Request resolved: https://github.com/facebook/react-native/pull/29407

Test Plan: Local tests (and iOS CI run) do not yield any errors, but I'm not sure how the CI pipeline would react to those changes. That is the reason why this PR is being posted as Draft. Some tweaks and code adjustment could be required.

Reviewed By: PeteTheHeat

Differential Revision: D22619441

Pulled By: shergin

fbshipit-source-id: 9aaf3840c5e8bd469c2cfcfa7c5b441ef71b30b6
2020-09-28 21:26:41 -07:00

471 lines
16 KiB
Objective-C

/*
* 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:(nullable NSNumber *)tag rootTag:(nullable NSNumber *)rootTag
{
RCTAssertMainQueue();
UIView *view = [self.manager view];
view.reactTag = tag;
view.rootTag = rootTag;
view.multipleTouchEnabled = YES;
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