RenderCore/View/CKComponentViewAttribute.mm (190 lines of code) (raw):
/*
* Copyright (c) 2014-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 "CKComponentViewAttribute.h"
#import <objc/runtime.h>
#import <unordered_map>
#import <RenderCore/RCAssert.h>
#import <RenderCore/RCAssert.h>
#import <RenderCore/RCEqualityHelpers.h>
#import <RenderCore/CKMacros.h>
/**
* Helper macro for asserting that an @encode type is the same size as
* a primitive type.
*/
#if DEBUG
#define RCCAssertSizeOfEquals(type, encodedType, ...) do { \
NSUInteger encodedTypeSize = 0; \
NSGetSizeAndAlignment((encodedType), &encodedTypeSize, nullptr); \
RCCAssert(sizeof(type) == encodedTypeSize, ##__VA_ARGS__); \
} while (0)
#else
#define RCCAssertSizeOfEquals(type, encodedType, ...) do {} while(0)
#endif
struct SetterCacheKey {
Class cls;
SEL sel;
bool operator==(const SetterCacheKey &other) const {
return cls == other.cls && sel_isEqual(sel, other.sel);
};
};
namespace std {
template<>
struct hash<SetterCacheKey>
{
std::size_t operator()(const SetterCacheKey &key) const
{
NSUInteger subhashes[] = { [key.cls hash], std::hash<void *>()((void *)key.sel) };
return RCIntegerArrayHash(subhashes, CK_ARRAY_COUNT(subhashes));
}
};
}
struct CachedSetter {
NSInvocation *invocation;
NSUInteger argumentSize;
const char *argumentType;
CachedSetter(NSInvocation *inv, NSUInteger argSize, const char *argType) : invocation(inv), argumentSize(argSize), argumentType(argType) {}
};
static const CachedSetter &cachedSetterInvocation(id object, SEL setter)
{
RCCAssertMainThread();
static auto *cachedInvocations = new std::unordered_map<SetterCacheKey, CachedSetter>();
SetterCacheKey key = {[object class], setter};
auto existingInvocation = cachedInvocations->find(key);
if (existingInvocation != cachedInvocations->end()) {
return existingInvocation->second;
}
NSMethodSignature *sig = [object methodSignatureForSelector:setter];
// If the setter actually takes an NSValue id as the argument, we shouldn't unbox to the primitive type.
const char *argumentType = [sig getArgumentTypeAtIndex:2];
if (strcmp(argumentType, @encode(id)) != 0) {
NSUInteger valueSize;
NSGetSizeAndAlignment(argumentType, &valueSize, NULL);
return cachedInvocations->emplace(key, CachedSetter([NSInvocation invocationWithMethodSignature:sig], valueSize, argumentType)).first->second;
} else {
return cachedInvocations->emplace(key, CachedSetter(nil, 0, nullptr)).first->second;
}
}
static void performSetter(id object, SEL setter, id value)
{
if ([value isKindOfClass:[NSValue class]]) {
const CachedSetter &set = cachedSetterInvocation(object, setter);
if (set.invocation) {
if ([value isKindOfClass:[NSNumber class]]) {
// We special case NSNumber because getting the correct byte width on both sides
// is either hard (e.g. NSInteger), or impossible (e.g. CGFloat) on all architectures
// simultaneously.
// See https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
// for more information on type encodings
if (set.argumentType == nullptr || strlen(set.argumentType) != 1) {
RCCAssert(NO, @"NSNumber: %@ cannot be used as an argument to a selector requiring '%s'; selector: %@; class %@",
value, set.argumentType ?: "NULL", NSStringFromSelector(setter), [object class]);
return;
}
NSNumber *numValue = (NSNumber *)value;
switch (*set.argumentType) {
case 'c': {
RCCAssertSizeOfEquals(char, set.argumentType, @"");
char charValue = [numValue charValue];
[set.invocation setArgument:&charValue atIndex:2];
break;
}
case 'i': {
RCCAssertSizeOfEquals(int, set.argumentType, @"");
int intValue = [numValue intValue];
[set.invocation setArgument:&intValue atIndex:2];
break;
}
case 's': {
RCCAssertSizeOfEquals(short, set.argumentType, @"");
short shortValue = [numValue shortValue];
[set.invocation setArgument:&shortValue atIndex:2];
break;
}
case 'l': {
// This is inconsistent, from the docs: "l is treated as a 32-bit quantity on 64-bit programs."
RCCAssertSizeOfEquals(int32_t, set.argumentType, @"");
int32_t longValue = [numValue intValue];
[set.invocation setArgument:&longValue atIndex:2];
break;
}
case 'q': {
RCCAssertSizeOfEquals(long long, set.argumentType, @"");
long long longLongValue = [numValue longLongValue];
[set.invocation setArgument:&longLongValue atIndex:2];
break;
}
case 'C': {
RCCAssertSizeOfEquals(unsigned char, set.argumentType, @"");
unsigned char uCharValue = [numValue unsignedCharValue];
[set.invocation setArgument:&uCharValue atIndex:2];
break;
}
case 'I': {
RCCAssertSizeOfEquals(unsigned int, set.argumentType, @"");
unsigned int uIntValue = [numValue unsignedIntValue];
[set.invocation setArgument:&uIntValue atIndex:2];
break;
}
case 'S': {
RCCAssertSizeOfEquals(unsigned short, set.argumentType, @"");
unsigned short uShortValue = [numValue unsignedShortValue];
[set.invocation setArgument:&uShortValue atIndex:2];
break;
}
case 'L': {
// This is also inconsistent, and undocumented
RCCAssertSizeOfEquals(uint32_t, set.argumentType, @"");
uint32_t uLongValue = [numValue unsignedIntValue];
[set.invocation setArgument:&uLongValue atIndex:2];
break;
}
case 'Q': {
RCCAssertSizeOfEquals(unsigned long long, set.argumentType, @"");
unsigned long long uLongLongValue = [numValue unsignedLongLongValue];
[set.invocation setArgument:&uLongLongValue atIndex:2];
break;
}
case 'f': {
RCCAssertSizeOfEquals(float, set.argumentType, @"");
float floatValue = [numValue floatValue];
[set.invocation setArgument:&floatValue atIndex:2];
break;
}
case 'd': {
RCCAssertSizeOfEquals(double, set.argumentType, @"");
double doubleValue = [numValue doubleValue];
[set.invocation setArgument:&doubleValue atIndex:2];
break;
}
case 'B': {
RCCAssertSizeOfEquals(BOOL, set.argumentType, @"");
BOOL boolValue = [numValue boolValue];
[set.invocation setArgument:&boolValue atIndex:2];
break;
}
default:
// This should just be: 'v', '*', '@', '#', ':', '?', none of which should be boxed as NSNumber
RCCAssert(NO, @"NSNumber: %@ cannot be used as an argument to a selector requiring '%s'", value, set.argumentType);
return;
}
} else {
char buf[set.argumentSize];
[value getValue:buf];
[set.invocation setArgument:buf atIndex:2];
}
[set.invocation setSelector:setter];
[set.invocation invokeWithTarget:object];
return;
}
}
// ARC is worried that the selector might have a return value it doesn't know about, or be annotated with ns_consumed.
// Neither is typically the case for setters, so ignore the warning.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[object performSelector:setter withObject:value];
#pragma clang diagnostic pop
}
CKComponentViewAttribute::CKComponentViewAttribute(const std::string &ident,
void (^app)(id view, id value),
void (^unapp)(id view, id value),
void (^upd)(id view, id oldValue, id newValue)) :
identifier(ident),
applicator(app),
unapplicator(unapp),
updater(upd) {};
CKComponentViewAttribute::CKComponentViewAttribute(SEL setter) noexcept :
identifier(sel_getName(setter)),
applicator(^(UIView *view, id value){
performSetter(view, setter, value);
}) {}
// Explicit destructor to prevent inlining, reduce code size. See D1814602.
CKComponentViewAttribute::~CKComponentViewAttribute() {}
CKComponentViewAttribute CKComponentViewAttribute::LayerAttribute(SEL setter) noexcept
{
return CKComponentViewAttribute(std::string("layer") + sel_getName(setter), ^(UIView *view, id value){
performSetter(view.layer, setter, value);
});
}
template class std::unordered_map<CKComponentViewAttribute, CKBoxedValue>;