mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
cb719a16cc
Summary: With the upgrade to React Native 0.63, we started running into nullability warnings that were breaking our build. This PR fixes those nullability warnings as well as a few other warnings in React-Core. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - Fix xcodebuild warnings in React-Core Pull Request resolved: https://github.com/facebook/react-native/pull/29622 Test Plan: - Nullability annotations should only affect compilation, but even though RNTester compiles, I'm not fully convinced that this won't break projects downstream. It would be good to get another opinion on this. - The change in `RCTAllocateRootViewTag` is the only real logic change in this PR. We throw an exception if the root view tag is not in the correct format, so this change seems safe after some basic manual testing in RNTester. Reviewed By: shergin Differential Revision: D23386678 Pulled By: appden fbshipit-source-id: a74875195a4614c3248e8f968aa98602e3ee2de0
473 lines
16 KiB
Objective-C
473 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;
|
|
#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
|