123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868 |
- //
- // FLEXRuntimeUtility.m
- // Flipboard
- //
- // Created by Ryan Olson on 6/8/14.
- // Copyright (c) 2020 FLEX Team. All rights reserved.
- //
- #import <UIKit/UIKit.h>
- #import "FLEXRuntimeUtility.h"
- #import "FLEXObjcInternal.h"
- #import "FLEXTypeEncodingParser.h"
- static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
- typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
- FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
- FLEXRuntimeUtilityErrorCodeInvocationFailed = 1,
- FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch = 2
- };
- @implementation FLEXRuntimeUtility
- #pragma mark - General Helpers (Public)
- + (BOOL)pointerIsValidObjcObject:(const void *)pointer {
- return FLEXPointerIsValidObjcObject(pointer);
- }
- + (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType {
- if (!returnedObjectOrNil) {
- return nil;
- }
- NSInteger i = 0;
- if (returnType[i] == FLEXTypeEncodingConst) {
- i++;
- }
- BOOL returnsObjectOrClass = returnType[i] == FLEXTypeEncodingObjcObject ||
- returnType[i] == FLEXTypeEncodingObjcClass;
- BOOL returnsVoidPointer = returnType[i] == FLEXTypeEncodingPointer &&
- returnType[i+1] == FLEXTypeEncodingVoid;
- BOOL returnsCString = returnType[i] == FLEXTypeEncodingCString;
- // If we got back an NSValue and the return type is not an object,
- // we check to see if the pointer is of a valid object. If not,
- // we just display the NSValue.
- if (!returnsObjectOrClass) {
- // Skip NSNumber instances
- if ([returnedObjectOrNil isKindOfClass:[NSNumber class]]) {
- return returnedObjectOrNil;
- }
-
- // Can only be NSValue since return type is not an object,
- // so we bail if this doesn't add up
- if (![returnedObjectOrNil isKindOfClass:[NSValue class]]) {
- return returnedObjectOrNil;
- }
- NSValue *value = (NSValue *)returnedObjectOrNil;
- if (returnsCString) {
- // Wrap char * in NSString
- const char *string = (const char *)value.pointerValue;
- returnedObjectOrNil = string ? [NSString stringWithCString:string encoding:NSUTF8StringEncoding] : NULL;
- } else if (returnsVoidPointer) {
- // Cast valid objects disguised as void * to id
- if ([FLEXRuntimeUtility pointerIsValidObjcObject:value.pointerValue]) {
- returnedObjectOrNil = (__bridge id)value.pointerValue;
- }
- }
- }
- return returnedObjectOrNil;
- }
- + (NSUInteger)fieldNameOffsetForTypeEncoding:(const FLEXTypeEncoding *)typeEncoding {
- NSUInteger beginIndex = 0;
- while (typeEncoding[beginIndex] == FLEXTypeEncodingQuote) {
- NSUInteger endIndex = beginIndex + 1;
- while (typeEncoding[endIndex] != FLEXTypeEncodingQuote) {
- ++endIndex;
- }
- beginIndex = endIndex + 1;
- }
- return beginIndex;
- }
- + (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass {
- NSMutableArray<Class> *superClasses = [NSMutableArray new];
- id cls = [objectOrClass class];
- do {
- [superClasses addObject:cls];
- } while ((cls = [cls superclass]));
- return superClasses;
- }
- + (NSString *)safeClassNameForObject:(id)object {
- // Don't assume that we have an NSObject subclass
- if ([self safeObject:object respondsToSelector:@selector(class)]) {
- return NSStringFromClass([object class]);
- }
- return NSStringFromClass(object_getClass(object));
- }
- /// Could be nil
- + (NSString *)safeDescriptionForObject:(id)object {
- // Don't assume that we have an NSObject subclass; not all objects respond to -description
- if ([self safeObject:object respondsToSelector:@selector(description)]) {
- return [object description];
- }
- return nil;
- }
- /// Never nil
- + (NSString *)safeDebugDescriptionForObject:(id)object {
- NSString *description = nil;
- if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) {
- description = [object debugDescription];
- } else {
- description = [self safeDescriptionForObject:object];
- }
- if (!description.length) {
- NSString *cls = NSStringFromClass(object_getClass(object));
- if (object_isClass(object)) {
- description = [cls stringByAppendingString:@" class (no description)"];
- } else {
- description = [cls stringByAppendingString:@" instance (no description)"];
- }
- }
- return description;
- }
- + (NSString *)summaryForObject:(id)value {
- NSString *description = nil;
- // Special case BOOL for better readability.
- if ([self safeObject:value isKindOfClass:[NSValue class]]) {
- const char *type = [value objCType];
- if (strcmp(type, @encode(BOOL)) == 0) {
- BOOL boolValue = NO;
- [value getValue:&boolValue];
- return boolValue ? @"YES" : @"NO";
- } else if (strcmp(type, @encode(SEL)) == 0) {
- SEL selector = NULL;
- [value getValue:&selector];
- return NSStringFromSelector(selector);
- }
- }
- @try {
- // Single line display - replace newlines and tabs with spaces.
- description = [[self safeDescriptionForObject:value] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
- description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
- } @catch (NSException *e) {
- description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"];
- }
- if (!description) {
- description = @"nil";
- }
- return description;
- }
- + (BOOL)safeObject:(id)object isKindOfClass:(Class)cls {
- static BOOL (*isKindOfClass)(id, SEL, Class) = nil;
- static BOOL (*isKindOfClass_meta)(id, SEL, Class) = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- isKindOfClass = (BOOL(*)(id, SEL, Class))[NSObject instanceMethodForSelector:@selector(isKindOfClass:)];
- isKindOfClass_meta = (BOOL(*)(id, SEL, Class))[NSObject methodForSelector:@selector(isKindOfClass:)];
- });
-
- BOOL isClass = object_isClass(object);
- return (isClass ? isKindOfClass_meta : isKindOfClass)(object, @selector(isKindOfClass:), cls);
- }
- + (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel {
- // If we're given a class, we want to know if classes respond to this selector.
- // Similarly, if we're given an instance, we want to know if instances respond.
- BOOL isClass = object_isClass(object);
- Class cls = isClass ? object : object_getClass(object);
- // BOOL isMetaclass = class_isMetaClass(cls);
-
- if (isClass) {
- // In theory, this should also work for metaclasses...
- return class_getClassMethod(cls, sel) != nil;
- } else {
- return class_getInstanceMethod(cls, sel) != nil;
- }
- }
- #pragma mark - Property Helpers (Public)
- + (BOOL)tryAddPropertyWithName:(const char *)name
- attributes:(NSDictionary<NSString *, NSString *> *)attributePairs
- toClass:(__unsafe_unretained Class)theClass {
- objc_property_t property = class_getProperty(theClass, name);
- if (!property) {
- unsigned int totalAttributesCount = (unsigned int)attributePairs.count;
- objc_property_attribute_t *attributes = malloc(sizeof(objc_property_attribute_t) * totalAttributesCount);
- if (attributes) {
- unsigned int attributeIndex = 0;
- for (NSString *attributeName in attributePairs.allKeys) {
- objc_property_attribute_t attribute;
- attribute.name = attributeName.UTF8String;
- attribute.value = attributePairs[attributeName].UTF8String;
- attributes[attributeIndex++] = attribute;
- }
- BOOL success = class_addProperty(theClass, name, attributes, totalAttributesCount);
- free(attributes);
- return success;
- } else {
- return NO;
- }
- }
-
- return YES;
- }
- + (NSArray<NSString *> *)allPropertyAttributeKeys {
- static NSArray<NSString *> *allPropertyAttributeKeys = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- allPropertyAttributeKeys = @[
- kFLEXPropertyAttributeKeyTypeEncoding,
- kFLEXPropertyAttributeKeyBackingIvarName,
- kFLEXPropertyAttributeKeyReadOnly,
- kFLEXPropertyAttributeKeyCopy,
- kFLEXPropertyAttributeKeyRetain,
- kFLEXPropertyAttributeKeyNonAtomic,
- kFLEXPropertyAttributeKeyCustomGetter,
- kFLEXPropertyAttributeKeyCustomSetter,
- kFLEXPropertyAttributeKeyDynamic,
- kFLEXPropertyAttributeKeyWeak,
- kFLEXPropertyAttributeKeyGarbageCollectable,
- kFLEXPropertyAttributeKeyOldStyleTypeEncoding,
- ];
- });
- return allPropertyAttributeKeys;
- }
- #pragma mark - Method Helpers (Public)
- + (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method {
- NSMutableArray<NSString *> *components = [NSMutableArray new];
- NSString *selectorName = NSStringFromSelector(method_getName(method));
- NSMutableArray<NSString *> *selectorComponents = [selectorName componentsSeparatedByString:@":"].mutableCopy;
- // this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods
- if (selectorComponents.count == 1) {
- return @[];
- }
- if ([selectorComponents.lastObject isEqualToString:@""]) {
- [selectorComponents removeLastObject];
- }
- for (unsigned int argIndex = 0; argIndex < selectorComponents.count; argIndex++) {
- char *argType = method_copyArgumentType(method, argIndex + kFLEXNumberOfImplicitArgs);
- NSString *readableArgType = (argType != NULL) ? [self readableTypeForEncoding:@(argType)] : nil;
- free(argType);
- NSString *prettyComponent = [NSString
- stringWithFormat:@"%@:(%@) ",
- selectorComponents[argIndex],
- readableArgType
- ];
- [components addObject:prettyComponent];
- }
- return components;
- }
- #pragma mark - Method Calling/Field Editing (Public)
- + (id)performSelector:(SEL)selector onObject:(id)object {
- return [self performSelector:selector onObject:object withArguments:@[] error:nil];
- }
- + (id)performSelector:(SEL)selector
- onObject:(id)object
- withArguments:(NSArray *)arguments
- error:(NSError * __autoreleasing *)error {
- static dispatch_once_t onceToken;
- static SEL stdStringExclusion = nil;
- dispatch_once(&onceToken, ^{
- stdStringExclusion = NSSelectorFromString(@"stdString");
- });
- // Bail if the object won't respond to this selector.
- if (![self safeObject:object respondsToSelector:selector]) {
- if (error) {
- NSString *msg = [NSString
- stringWithFormat:@"%@ does not respond to the selector %@",
- object, NSStringFromSelector(selector)
- ];
- NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
- *error = [NSError
- errorWithDomain:FLEXRuntimeUtilityErrorDomain
- code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector
- userInfo:userInfo
- ];
- }
- return nil;
- }
- NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:({
- Method method;
- if (object_isClass(object)) {
- method = class_getClassMethod(object, selector);
- } else {
- method = class_getInstanceMethod(object_getClass(object), selector);
- }
- method_getTypeEncoding(method);
- })];
-
- // Probably an unsupported type encoding, like bitfields.
- // In the future, we could calculate the return length
- // on our own. For now, we abort.
- //
- // For future reference, the code here will get the true type encoding.
- // NSMethodSignature will convert {?=b8b4b1b1b18[8S]} to {?}
- //
- // returnType = method_getTypeEncoding(class_getInstanceMethod([object class], selector));
- if (!methodSignature.methodReturnLength &&
- methodSignature.methodReturnType[0] != FLEXTypeEncodingVoid) {
- return nil;
- }
- // Build the invocation
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- [invocation setSelector:selector];
- [invocation setTarget:object];
- [invocation retainArguments];
- // Always self and _cmd
- NSUInteger numberOfArguments = [methodSignature numberOfArguments];
- for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
- NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
- id argumentObject = arguments.count > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil;
- // NSNull in the arguments array can be passed as a placeholder to indicate nil.
- // We only need to set the argument if it will be non-nil.
- if (argumentObject && ![argumentObject isKindOfClass:[NSNull class]]) {
- const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
- if (typeEncodingCString[0] == FLEXTypeEncodingObjcObject ||
- typeEncodingCString[0] == FLEXTypeEncodingObjcClass ||
- [self isTollFreeBridgedValue:argumentObject forCFType:typeEncodingCString]) {
- // Object
- [invocation setArgument:&argumentObject atIndex:argumentIndex];
- } else if (strcmp(typeEncodingCString, @encode(CGColorRef)) == 0 &&
- [argumentObject isKindOfClass:[UIColor class]]) {
- // Bridging UIColor to CGColorRef
- CGColorRef colorRef = [argumentObject CGColor];
- [invocation setArgument:&colorRef atIndex:argumentIndex];
- } else if ([argumentObject isKindOfClass:[NSValue class]]) {
- // Primitive boxed in NSValue
- NSValue *argumentValue = (NSValue *)argumentObject;
- // Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature
- if (strcmp([argumentValue objCType], typeEncodingCString) != 0) {
- if (error) {
- NSString *msg = [NSString
- stringWithFormat:@"Type encoding mismatch for argument at index %lu. "
- "Value type: %s; Method argument type: %s.",
- (unsigned long)argumentsArrayIndex, argumentValue.objCType, typeEncodingCString
- ];
- NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
- *error = [NSError
- errorWithDomain:FLEXRuntimeUtilityErrorDomain
- code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch
- userInfo:userInfo
- ];
- }
- return nil;
- }
- @try {
- NSUInteger bufferSize = 0;
- FLEXGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
- if (bufferSize > 0) {
- void *buffer = alloca(bufferSize);
- [argumentValue getValue:buffer];
- [invocation setArgument:buffer atIndex:argumentIndex];
- }
- } @catch (NSException *exception) { }
- }
- }
- }
- // Try to invoke the invocation but guard against an exception being thrown.
- id returnObject = nil;
- @try {
- [invocation invoke];
- // Retrieve the return value and box if necessary.
- const char *returnType = methodSignature.methodReturnType;
- if (returnType[0] == FLEXTypeEncodingObjcObject || returnType[0] == FLEXTypeEncodingObjcClass) {
- // Return value is an object.
- __unsafe_unretained id objectReturnedFromMethod = nil;
- [invocation getReturnValue:&objectReturnedFromMethod];
- returnObject = objectReturnedFromMethod;
- } else if (returnType[0] != FLEXTypeEncodingVoid) {
- NSAssert(methodSignature.methodReturnLength, @"Memory corruption lies ahead");
- if (returnType[0] == FLEXTypeEncodingStructBegin) {
- if (selector == stdStringExclusion && [object isKindOfClass:[NSString class]]) {
- // stdString is a C++ object and we will crash if we try to access it
- if (error) {
- *error = [NSError
- errorWithDomain:FLEXRuntimeUtilityErrorDomain
- code:FLEXRuntimeUtilityErrorCodeInvocationFailed
- userInfo:@{ NSLocalizedDescriptionKey : @"Skipping -[NSString stdString]" }
- ];
- }
- return nil;
- }
- }
- // Will use arbitrary buffer for return value and box it.
- void *returnValue = malloc(methodSignature.methodReturnLength);
- [invocation getReturnValue:returnValue];
- returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
- free(returnValue);
- }
- } @catch (NSException *exception) {
- // Bummer...
- if (error) {
- // "… on <class>" / "… on instance of <class>"
- NSString *class = NSStringFromClass([object class]);
- NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class];
- NSString *message = [NSString
- stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@",
- exception.name, NSStringFromSelector(selector), calledOn, exception.reason
- ];
- *error = [NSError
- errorWithDomain:FLEXRuntimeUtilityErrorDomain
- code:FLEXRuntimeUtilityErrorCodeInvocationFailed
- userInfo:@{ NSLocalizedDescriptionKey : message }
- ];
- }
- }
- return returnObject;
- }
- + (BOOL)isTollFreeBridgedValue:(id)value forCFType:(const char *)typeEncoding {
- // See https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html
- #define CASE(cftype, foundationClass) \
- if (strcmp(typeEncoding, @encode(cftype)) == 0) { \
- return [value isKindOfClass:[foundationClass class]]; \
- }
- CASE(CFArrayRef, NSArray);
- CASE(CFAttributedStringRef, NSAttributedString);
- CASE(CFCalendarRef, NSCalendar);
- CASE(CFCharacterSetRef, NSCharacterSet);
- CASE(CFDataRef, NSData);
- CASE(CFDateRef, NSDate);
- CASE(CFDictionaryRef, NSDictionary);
- CASE(CFErrorRef, NSError);
- CASE(CFLocaleRef, NSLocale);
- CASE(CFMutableArrayRef, NSMutableArray);
- CASE(CFMutableAttributedStringRef, NSMutableAttributedString);
- CASE(CFMutableCharacterSetRef, NSMutableCharacterSet);
- CASE(CFMutableDataRef, NSMutableData);
- CASE(CFMutableDictionaryRef, NSMutableDictionary);
- CASE(CFMutableSetRef, NSMutableSet);
- CASE(CFMutableStringRef, NSMutableString);
- CASE(CFNumberRef, NSNumber);
- CASE(CFReadStreamRef, NSInputStream);
- CASE(CFRunLoopTimerRef, NSTimer);
- CASE(CFSetRef, NSSet);
- CASE(CFStringRef, NSString);
- CASE(CFTimeZoneRef, NSTimeZone);
- CASE(CFURLRef, NSURL);
- CASE(CFWriteStreamRef, NSOutputStream);
- #undef CASE
- return NO;
- }
- + (NSString *)editableJSONStringForObject:(id)object {
- NSString *editableDescription = nil;
- if (object) {
- // This is a hack to use JSON serialization for our editable objects.
- // NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary.
- // We always wrap the object inside an array and then strip the outer square braces off the final string.
- NSArray *wrappedObject = @[object];
- if ([NSJSONSerialization isValidJSONObject:wrappedObject]) {
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:wrappedObject options:0 error:NULL];
- NSString *wrappedDescription = [NSString stringWithUTF8String:jsonData.bytes];
- editableDescription = [wrappedDescription substringWithRange:NSMakeRange(1, wrappedDescription.length - 2)];
- }
- }
- return editableDescription;
- }
- + (id)objectValueFromEditableJSONString:(NSString *)string {
- id value = nil;
- // nil for empty string/whitespace
- if ([string stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet].length) {
- value = [NSJSONSerialization
- JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding]
- options:NSJSONReadingAllowFragments
- error:NULL
- ];
- }
- return value;
- }
- + (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString {
- NSNumberFormatter *formatter = [NSNumberFormatter new];
- [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
- NSNumber *number = [formatter numberFromString:inputString];
-
- // Is the type encoding longer than one character?
- if (strlen(typeEncoding) > 1) {
- NSString *type = @(typeEncoding);
-
- // Is it NSDecimalNumber or NSNumber?
- if ([type isEqualToString:@FLEXEncodeClass(NSDecimalNumber)]) {
- return [NSDecimalNumber decimalNumberWithString:inputString];
- } else if ([type isEqualToString:@FLEXEncodeClass(NSNumber)]) {
- return number;
- }
-
- return nil;
- }
-
- // Type encoding is one character, switch on the type
- FLEXTypeEncoding type = typeEncoding[0];
- uint8_t value[32];
- void *bufferStart = &value[0];
-
- // Make sure we box the number with the correct type encoding
- // so it can be properly unboxed later via getValue:
- switch (type) {
- case FLEXTypeEncodingChar:
- *(char *)bufferStart = number.charValue; break;
- case FLEXTypeEncodingInt:
- *(int *)bufferStart = number.intValue; break;
- case FLEXTypeEncodingShort:
- *(short *)bufferStart = number.shortValue; break;
- case FLEXTypeEncodingLong:
- *(long *)bufferStart = number.longValue; break;
- case FLEXTypeEncodingLongLong:
- *(long long *)bufferStart = number.longLongValue; break;
- case FLEXTypeEncodingUnsignedChar:
- *(unsigned char *)bufferStart = number.unsignedCharValue; break;
- case FLEXTypeEncodingUnsignedInt:
- *(unsigned int *)bufferStart = number.unsignedIntValue; break;
- case FLEXTypeEncodingUnsignedShort:
- *(unsigned short *)bufferStart = number.unsignedShortValue; break;
- case FLEXTypeEncodingUnsignedLong:
- *(unsigned long *)bufferStart = number.unsignedLongValue; break;
- case FLEXTypeEncodingUnsignedLongLong:
- *(unsigned long long *)bufferStart = number.unsignedLongLongValue; break;
- case FLEXTypeEncodingFloat:
- *(float *)bufferStart = number.floatValue; break;
- case FLEXTypeEncodingDouble:
- *(double *)bufferStart = number.doubleValue; break;
-
- case FLEXTypeEncodingLongDouble:
- // NSNumber does not support long double
- default:
- return nil;
- }
-
- return [NSValue value:value withObjCType:typeEncoding];
- }
- + (void)enumerateTypesInStructEncoding:(const char *)structEncoding
- usingBlock:(void (^)(NSString *structName,
- const char *fieldTypeEncoding,
- NSString *prettyTypeEncoding,
- NSUInteger fieldIndex,
- NSUInteger fieldOffset))typeBlock {
- if (structEncoding && structEncoding[0] == FLEXTypeEncodingStructBegin) {
- const char *equals = strchr(structEncoding, '=');
- if (equals) {
- const char *nameStart = structEncoding + 1;
- NSString *structName = [@(structEncoding)
- substringWithRange:NSMakeRange(nameStart - structEncoding, equals - nameStart)
- ];
- NSUInteger fieldAlignment = 0, structSize = 0;
- if (FLEXGetSizeAndAlignment(structEncoding, &structSize, &fieldAlignment)) {
- NSUInteger runningFieldIndex = 0;
- NSUInteger runningFieldOffset = 0;
- const char *typeStart = equals + 1;
-
- while (*typeStart != FLEXTypeEncodingStructEnd) {
- NSUInteger fieldSize = 0;
- // If the struct type encoding was successfully handled by
- // FLEXGetSizeAndAlignment above, we *should* be ok with the field here.
- const char *nextTypeStart = NSGetSizeAndAlignment(typeStart, &fieldSize, NULL);
- NSString *typeEncoding = [@(structEncoding)
- substringWithRange:NSMakeRange(typeStart - structEncoding, nextTypeStart - typeStart)
- ];
-
- // Padding to keep proper alignment. __attribute((packed)) structs
- // will break here. The type encoding is no different for packed structs,
- // so it's not clear there's anything we can do for those.
- const NSUInteger currentSizeSum = runningFieldOffset % fieldAlignment;
- if (currentSizeSum != 0 && currentSizeSum + fieldSize > fieldAlignment) {
- runningFieldOffset += fieldAlignment - currentSizeSum;
- }
-
- typeBlock(
- structName,
- typeEncoding.UTF8String,
- [self readableTypeForEncoding:typeEncoding],
- runningFieldIndex,
- runningFieldOffset
- );
- runningFieldOffset += fieldSize;
- runningFieldIndex++;
- typeStart = nextTypeStart;
- }
- }
- }
- }
- }
- #pragma mark - Metadata Helpers
- + (NSDictionary<NSString *, NSString *> *)attributesForProperty:(objc_property_t)property {
- NSString *attributes = @(property_getAttributes(property) ?: "");
- // Thanks to MAObjcRuntime for inspiration here.
- NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","];
- NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary new];
- for (NSString *attributePair in attributePairs) {
- attributesDictionary[[attributePair substringToIndex:1]] = [attributePair substringFromIndex:1];
- }
- return attributesDictionary;
- }
- + (NSString *)appendName:(NSString *)name toType:(NSString *)type {
- if (!type.length) {
- type = @"(?)";
- }
-
- NSString *combined = nil;
- if ([type characterAtIndex:type.length - 1] == FLEXTypeEncodingCString) {
- combined = [type stringByAppendingString:name];
- } else {
- combined = [type stringByAppendingFormat:@" %@", name];
- }
- return combined;
- }
- + (NSString *)readableTypeForEncoding:(NSString *)encodingString {
- if (!encodingString.length) {
- return @"?";
- }
- // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
- // class-dump has a much nicer and much more complete implementation for this task, but it is distributed under GPLv2 :/
- // See https://github.com/nygard/class-dump/blob/master/Source/CDType.m
- // Warning: this method uses multiple middle returns and macros to cut down on boilerplate.
- // The use of macros here was inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
- const char *encodingCString = encodingString.UTF8String;
- // Some fields have a name, such as {Size=\"width\"d\"height\"d}, we need to extract the name out and recursive
- const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:encodingCString];
- if (fieldNameOffset > 0) {
- // According to https://github.com/nygard/class-dump/commit/33fb5ed221810685f57c192e1ce8ab6054949a7c,
- // there are some consecutive quoted strings, so use `_` to concatenate the names.
- NSString *const fieldNamesString = [encodingString substringWithRange:NSMakeRange(0, fieldNameOffset)];
- NSArray<NSString *> *const fieldNames = [fieldNamesString
- componentsSeparatedByString:[NSString stringWithFormat:@"%c", FLEXTypeEncodingQuote]
- ];
- NSMutableString *finalFieldNamesString = [NSMutableString new];
- for (NSString *const fieldName in fieldNames) {
- if (fieldName.length > 0) {
- if (finalFieldNamesString.length > 0) {
- [finalFieldNamesString appendString:@"_"];
- }
- [finalFieldNamesString appendString:fieldName];
- }
- }
- NSString *const recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:fieldNameOffset]];
- return [NSString stringWithFormat:@"%@ %@", recursiveType, finalFieldNamesString];
- }
- // Objects
- if (encodingCString[0] == FLEXTypeEncodingObjcObject) {
- NSString *class = [encodingString substringFromIndex:1];
- class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""];
- if (class.length == 0 || (class.length == 1 && [class characterAtIndex:0] == FLEXTypeEncodingUnknown)) {
- class = @"id";
- } else {
- class = [class stringByAppendingString:@" *"];
- }
- return class;
- }
- // Qualifier Prefixes
- // Do this first since some of the direct translations (i.e. Method) contain a prefix.
- #define RECURSIVE_TRANSLATE(prefix, formatString) \
- if (encodingCString[0] == prefix) { \
- NSString *recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:1]]; \
- return [NSString stringWithFormat:formatString, recursiveType]; \
- }
- // If there's a qualifier prefix on the encoding, translate it and then
- // recursively call this method with the rest of the encoding string.
- RECURSIVE_TRANSLATE('^', @"%@ *");
- RECURSIVE_TRANSLATE('r', @"const %@");
- RECURSIVE_TRANSLATE('n', @"in %@");
- RECURSIVE_TRANSLATE('N', @"inout %@");
- RECURSIVE_TRANSLATE('o', @"out %@");
- RECURSIVE_TRANSLATE('O', @"bycopy %@");
- RECURSIVE_TRANSLATE('R', @"byref %@");
- RECURSIVE_TRANSLATE('V', @"oneway %@");
- RECURSIVE_TRANSLATE('b', @"bitfield(%@)");
- #undef RECURSIVE_TRANSLATE
- // C Types
- #define TRANSLATE(ctype) \
- if (strcmp(encodingCString, @encode(ctype)) == 0) { \
- return (NSString *)CFSTR(#ctype); \
- }
- // Order matters here since some of the cocoa types are typedefed to c types.
- // We can't recover the exact mapping, but we choose to prefer the cocoa types.
- // This is not an exhaustive list, but it covers the most common types
- TRANSLATE(CGRect);
- TRANSLATE(CGPoint);
- TRANSLATE(CGSize);
- TRANSLATE(CGVector);
- TRANSLATE(UIEdgeInsets);
- if (@available(iOS 11.0, *)) {
- TRANSLATE(NSDirectionalEdgeInsets);
- }
- TRANSLATE(UIOffset);
- TRANSLATE(NSRange);
- TRANSLATE(CGAffineTransform);
- TRANSLATE(CATransform3D);
- TRANSLATE(CGColorRef);
- TRANSLATE(CGPathRef);
- TRANSLATE(CGContextRef);
- TRANSLATE(NSInteger);
- TRANSLATE(NSUInteger);
- TRANSLATE(CGFloat);
- TRANSLATE(BOOL);
- TRANSLATE(int);
- TRANSLATE(short);
- TRANSLATE(long);
- TRANSLATE(long long);
- TRANSLATE(unsigned char);
- TRANSLATE(unsigned int);
- TRANSLATE(unsigned short);
- TRANSLATE(unsigned long);
- TRANSLATE(unsigned long long);
- TRANSLATE(float);
- TRANSLATE(double);
- TRANSLATE(long double);
- TRANSLATE(char *);
- TRANSLATE(Class);
- TRANSLATE(objc_property_t);
- TRANSLATE(Ivar);
- TRANSLATE(Method);
- TRANSLATE(Category);
- TRANSLATE(NSZone *);
- TRANSLATE(SEL);
- TRANSLATE(void);
- #undef TRANSLATE
- // For structs, we only use the name of the structs
- if (encodingCString[0] == FLEXTypeEncodingStructBegin) {
- // Special case: std::string
- if ([encodingString hasPrefix:@"{basic_string<char"]) {
- return @"std::string";
- }
- const char *equals = strchr(encodingCString, '=');
- if (equals) {
- const char *nameStart = encodingCString + 1;
- // For anonymous structs
- if (nameStart[0] == FLEXTypeEncodingUnknown) {
- return @"anonymous struct";
- } else {
- NSString *const structName = [encodingString
- substringWithRange:NSMakeRange(nameStart - encodingCString, equals - nameStart)
- ];
- return structName;
- }
- }
- }
- // If we couldn't translate, just return the original encoding string
- return encodingString;
- }
- #pragma mark - Internal Helpers
- + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type {
- // Remove the field name if there is any (e.g. \"width\"d -> d)
- const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:type];
- if (fieldNameOffset > 0) {
- return [self valueForPrimitivePointer:pointer objCType:type + fieldNameOffset];
- }
- // CASE macro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
- #define CASE(ctype, selectorpart) \
- if (strcmp(type, @encode(ctype)) == 0) { \
- return [NSNumber numberWith ## selectorpart: *(ctype *)pointer]; \
- }
- CASE(BOOL, Bool);
- CASE(unsigned char, UnsignedChar);
- CASE(short, Short);
- CASE(unsigned short, UnsignedShort);
- CASE(int, Int);
- CASE(unsigned int, UnsignedInt);
- CASE(long, Long);
- CASE(unsigned long, UnsignedLong);
- CASE(long long, LongLong);
- CASE(unsigned long long, UnsignedLongLong);
- CASE(float, Float);
- CASE(double, Double);
- CASE(long double, Double);
- #undef CASE
- NSValue *value = nil;
- if (FLEXGetSizeAndAlignment(type, nil, nil)) {
- @try {
- value = [NSValue valueWithBytes:pointer objCType:type];
- } @catch (NSException *exception) {
- // Certain type encodings are not supported by valueWithBytes:objCType:.
- // Just fail silently if an exception is thrown.
- }
- }
- return value;
- }
- @end
|