mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 04:44:14 -04:00 
			
		
		
		
	added files via upload
This commit is contained in:
		
							
								
								
									
										881
									
								
								Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										881
									
								
								Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,881 @@ | ||||
| // | ||||
| //  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" | ||||
| #import "FLEXMethod.h" | ||||
|  | ||||
| NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain"; | ||||
|  | ||||
| @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)]) { | ||||
|         @try { | ||||
|             return [object description]; | ||||
|         } @catch (NSException *exception) { | ||||
|             return nil; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| /// Never nil | ||||
| + (NSString *)safeDebugDescriptionForObject:(id)object { | ||||
|     NSString *description = nil; | ||||
|  | ||||
|     if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) { | ||||
|         @try { | ||||
|             description = [object debugDescription]; | ||||
|         } @catch (NSException *exception) { } | ||||
|     } 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 { | ||||
|     return [self performSelector:selector | ||||
|         onObject:object | ||||
|         withArguments:arguments | ||||
|         allowForwarding:NO | ||||
|         error:error | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| + (id)performSelector:(SEL)selector | ||||
|              onObject:(id)object | ||||
|         withArguments:(NSArray *)arguments | ||||
|       allowForwarding:(BOOL)mightForwardMsgSend | ||||
|                 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 (mightForwardMsgSend || ![self safeObject:object respondsToSelector:selector]) { | ||||
|         if (error) { | ||||
|             NSString *msg = [NSString | ||||
|                 stringWithFormat:@"This object does not respond to the selector %@", | ||||
|                 NSStringFromSelector(selector) | ||||
|             ]; | ||||
|             NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg }; | ||||
|             *error = [NSError | ||||
|                 errorWithDomain:FLEXRuntimeUtilityErrorDomain | ||||
|                 code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector | ||||
|                 userInfo:userInfo | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     // It is important to use object_getClass and not -class here, as | ||||
|     // object_getClass will return a different result for class objects | ||||
|     Class cls = object_getClass(object); | ||||
|     NSMethodSignature *methodSignature = [FLEXMethod selector:selector class:cls].signature; | ||||
|     if (!methodSignature) { | ||||
|         // Unsupported type encoding | ||||
|         return nil; | ||||
|     } | ||||
|      | ||||
|     // 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 | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn