// Copyright © 2018 650 Industries. All rights reserved.
#import <UMCore/UMViewManager.h>
#import <objc/runtime.h>
#define QUOTE(str) #str
#define EXPAND_AND_QUOTE(str) QUOTE(str)
#define UM_IS_METHOD_PROPSETTER(methodName) \
[methodName hasPrefix:@EXPAND_AND_QUOTE(UM_PROPSETTERS_PREFIX)]
#define UM_PROPSETTER_FOR_PROP(propName)\
QUOTE(UM_PROPSETTERS_PREFIX)propName
static const NSString *noViewExceptionName = @"No custom -(UIView *)view implementation.";
static const NSString *noViewExceptionReason = @"You've subclassed an UMViewManager, but didn't override the -(UIView *)view method. Override this method and return a new view instance.";
static const NSString *noViewNameExceptionName = @"No custom -(NSString *)viewName implementation.";
static const NSString *noViewNameExceptionReasonFormat = @"You've subclassed an UMViewManager in %@, but didn't override the -(NSString *)viewName method. Override this method and return a name of the view component.";
@interface UMViewManager ()
@property NSDictionary<NSString *, NSString *> *propsNamesSelectors;
@end
@implementation UMViewManager
- (instancetype)init
{
if (self = [super init]) {
_propsNamesSelectors = [self getPropsNames];
}
return self;
}
- (UIView *)view
{
@throw [NSException exceptionWithName:(NSString *)noViewExceptionName
reason:(NSString *)noViewExceptionReason
userInfo:nil];
}
- (NSString *)viewName
{
@throw [NSException exceptionWithName:(NSString *)noViewNameExceptionName
reason:(NSString *)[NSString stringWithFormat:(NSString *)noViewNameExceptionReasonFormat, NSStringFromClass([self class])]
userInfo:nil];
}
- (NSArray<NSString *> *)supportedEvents
{
return @[];
}
// Scans the class methods for methods with a certain prefix (see macro UM_PROPSETTERS_PREFIX),
// and returns dictionary which has props names as keys and selector strings as values.
// Example: @{ @"type": @"__ex_set__type" }
- (NSDictionary<NSString *, NSString *> *)getPropsNames
{
NSMutableDictionary<NSString *, NSString *> *propsNames = [NSMutableDictionary dictionary];
unsigned int methodsCount;
Method *methodsDescriptions = class_copyMethodList([self class], &methodsCount);
@try {
for(int i = 0; i < methodsCount; i++) {
Method method = methodsDescriptions[i];
SEL methodSelector = method_getName(method);
NSString *methodName = NSStringFromSelector(methodSelector);
if (UM_IS_METHOD_PROPSETTER(methodName)) {
NSString *propNameWithArguments = [methodName substringFromIndex:[@EXPAND_AND_QUOTE(UM_PROPSETTERS_PREFIX) length]];
NSString *propName = [[propNameWithArguments componentsSeparatedByString:@":"] firstObject];
propsNames[propName] = methodName;
}
}
}
@finally {
free(methodsDescriptions);
}
return propsNames;
}
- (void)updateProp:(NSString *)propName withValue:(id)value onView:(UIView *)view
{
if (_propsNamesSelectors[propName]) {
NSString *selectorString = _propsNamesSelectors[propName];
SEL selector = NSSelectorFromString(selectorString);
NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector];
if (methodSignature == nil) {
// This in fact should never happen -- if we have a selector for this prop
// (which we have if we're here), view manager should return method signature
// for the cached selector.
UMLogError(@"View manager of view '%@' does not implement method for selector '%@'.", [self viewName], NSStringFromSelector(selector));
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:self];
[invocation setSelector:selector];
[invocation setArgument:&value atIndex:2];
// According to objc.h, the BOOL type can be represented by `bool` or `signed char` so
// getArgumentTypeAtIndex can return _C_BOOL (when `bool`) or _C_CHR (when `signed char`)
#if OBJC_BOOL_IS_BOOL
if ([methodSignature getArgumentTypeAtIndex:2][0] == _C_BOOL) {
// We need this intermediary variable, see
// https://stackoverflow.com/questions/11061166/pointer-to-bool-in-objective-c
BOOL retainedValue = [value boolValue];
[invocation setArgument:&retainedValue atIndex:2];
}
#else // BOOL is represented by `signed char`
if ([methodSignature getArgumentTypeAtIndex:2][0] == _C_CHR) {
BOOL retainedValue = [value charValue];
[invocation setArgument:&retainedValue atIndex:2];
}
#endif
[invocation setArgument:(void *)&view atIndex:3];
[invocation retainArguments];
[invocation invoke];
} else {
UMLogWarn(@"Tried to set property `%@` on view manager of view `%@` when the view manager does not export such prop.", propName, [self viewName]);
}
}
@end