This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
451 lines
16 KiB
451 lines
16 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* 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 "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; |
|
|
|
/** |
|
* 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 |
|
{ |
|
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; |
|
} |
|
|
|
- (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]; |
|
} |
|
((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, 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; |
|
} |
|
|
|
NSMutableDictionary *mutableEvent = [NSMutableDictionary dictionaryWithDictionary:event]; |
|
mutableEvent[@"target"] = strongTarget.reactTag; |
|
#pragma clang diagnostic push |
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations" |
|
[weakBridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(propName) body:mutableEvent]; |
|
#pragma clang diagnostic pop |
|
}; |
|
} |
|
((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); |
|
[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, _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
|
|
|