mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 04:44:14 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			379 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  FLEXObjectExplorer.m
 | |
| //  FLEX
 | |
| //
 | |
| //  Created by Tanner Bennett on 8/28/19.
 | |
| //  Copyright © 2020 FLEX Team. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "FLEXObjectExplorer.h"
 | |
| #import "FLEXUtility.h"
 | |
| #import "FLEXRuntimeUtility.h"
 | |
| #import "NSObject+FLEX_Reflection.h"
 | |
| #import "FLEXRuntime+Compare.h"
 | |
| #import "FLEXRuntime+UIKitHelpers.h"
 | |
| #import "FLEXPropertyAttributes.h"
 | |
| #import "FLEXMetadataSection.h"
 | |
| #import "NSUserDefaults+FLEX.h"
 | |
| 
 | |
| @implementation FLEXObjectExplorerDefaults
 | |
| 
 | |
| + (instancetype)canEdit:(BOOL)editable wantsPreviews:(BOOL)showPreviews {
 | |
|     FLEXObjectExplorerDefaults *defaults = [self new];
 | |
|     defaults->_isEditable = editable;
 | |
|     defaults->_wantsDynamicPreviews = showPreviews;
 | |
|     return defaults;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface FLEXObjectExplorer () {
 | |
|     NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
 | |
|     NSMutableArray<NSArray<FLEXProperty *> *> *_allClassProperties;
 | |
|     NSMutableArray<NSArray<FLEXIvar *> *> *_allIvars;
 | |
|     NSMutableArray<NSArray<FLEXMethod *> *> *_allMethods;
 | |
|     NSMutableArray<NSArray<FLEXMethod *> *> *_allClassMethods;
 | |
|     NSMutableArray<NSArray<FLEXProtocol *> *> *_allConformedProtocols;
 | |
|     NSMutableArray<FLEXStaticMetadata *> *_allInstanceSizes;
 | |
|     NSMutableArray<FLEXStaticMetadata *> *_allImageNames;
 | |
|     NSString *_objectDescription;
 | |
| }
 | |
| @end
 | |
| 
 | |
| @implementation FLEXObjectExplorer
 | |
| 
 | |
| #pragma mark - Initialization
 | |
| 
 | |
| + (id)forObject:(id)objectOrClass {
 | |
|     return [[self alloc] initWithObject:objectOrClass];
 | |
| }
 | |
| 
 | |
| - (id)initWithObject:(id)objectOrClass {
 | |
|     NSParameterAssert(objectOrClass);
 | |
|     
 | |
|     self = [super init];
 | |
|     if (self) {
 | |
|         _object = objectOrClass;
 | |
|         _objectIsInstance = !object_isClass(objectOrClass);
 | |
|         
 | |
|         [self reloadMetadata];
 | |
|     }
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - Public
 | |
| 
 | |
| + (void)configureDefaultsForItems:(NSArray<id<FLEXObjectExplorerItem>> *)items {
 | |
|     BOOL hidePreviews = NSUserDefaults.standardUserDefaults.flex_explorerHidesVariablePreviews;
 | |
|     FLEXObjectExplorerDefaults *mutable = [FLEXObjectExplorerDefaults
 | |
|         canEdit:YES wantsPreviews:!hidePreviews
 | |
|     ];
 | |
|     FLEXObjectExplorerDefaults *immutable = [FLEXObjectExplorerDefaults
 | |
|         canEdit:NO wantsPreviews:!hidePreviews
 | |
|     ];
 | |
| 
 | |
|     // .tag is used to cache whether the value of .isEditable;
 | |
|     // This could change at runtime so it is important that
 | |
|     // it is cached every time shortcuts are requeted and not
 | |
|     // just once at as shortcuts are initially registered
 | |
|     for (id<FLEXObjectExplorerItem> metadata in items) {
 | |
|         metadata.defaults = metadata.isEditable ? mutable : immutable;
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSString *)objectDescription {
 | |
|     if (!_objectDescription) {
 | |
|         // Hard-code UIColor description
 | |
|         if ([FLEXRuntimeUtility safeObject:self.object isKindOfClass:[UIColor class]]) {
 | |
|             CGFloat h, s, l, r, g, b, a;
 | |
|             [self.object getRed:&r green:&g blue:&b alpha:&a];
 | |
|             [self.object getHue:&h saturation:&s brightness:&l alpha:nil];
 | |
| 
 | |
|             return [NSString stringWithFormat:
 | |
|                 @"HSL: (%.3f, %.3f, %.3f)\nRGB: (%.3f, %.3f, %.3f)\nAlpha: %.3f",
 | |
|                 h, s, l, r, g, b, a
 | |
|             ];
 | |
|         }
 | |
| 
 | |
|         NSString *description = [FLEXRuntimeUtility safeDescriptionForObject:self.object];
 | |
| 
 | |
|         if (!description.length) {
 | |
|             NSString *address = [FLEXUtility addressOfObject:self.object];
 | |
|             return [NSString stringWithFormat:@"Object at %@ returned empty description", address];
 | |
|         }
 | |
|         
 | |
|         if (description.length > 10000) {
 | |
|             description = [description substringToIndex:10000];
 | |
|         }
 | |
| 
 | |
|         _objectDescription = description;
 | |
|     }
 | |
| 
 | |
|     return _objectDescription;
 | |
| }
 | |
| 
 | |
| - (void)setClassScope:(NSInteger)classScope {
 | |
|     _classScope = classScope;
 | |
|     
 | |
|     [self reloadScopedMetadata];
 | |
| }
 | |
| 
 | |
| - (void)reloadMetadata {
 | |
|     _allProperties = [NSMutableArray new];
 | |
|     _allClassProperties = [NSMutableArray new];
 | |
|     _allIvars = [NSMutableArray new];
 | |
|     _allMethods = [NSMutableArray new];
 | |
|     _allClassMethods = [NSMutableArray new];
 | |
|     _allConformedProtocols = [NSMutableArray new];
 | |
|     _allInstanceSizes = [NSMutableArray new];
 | |
|     _allImageNames = [NSMutableArray new];
 | |
|     _objectDescription = nil;
 | |
| 
 | |
|     [self reloadClassHierarchy];
 | |
|     
 | |
|     NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
 | |
|     BOOL hideBackingIvars = defaults.flex_explorerHidesPropertyIvars;
 | |
|     BOOL hidePropertyMethods = defaults.flex_explorerHidesPropertyMethods;
 | |
|     BOOL hidePrivateMethods = defaults.flex_explorerHidesPrivateMethods;
 | |
|     BOOL showMethodOverrides = defaults.flex_explorerShowsMethodOverrides;
 | |
|     
 | |
|     NSMutableArray<NSArray<FLEXProperty *> *> *allProperties = [NSMutableArray new];
 | |
|     NSMutableArray<NSArray<FLEXProperty *> *> *allClassProps = [NSMutableArray new];
 | |
|     NSMutableArray<NSArray<FLEXMethod *> *> *allMethods = [NSMutableArray new];
 | |
|     NSMutableArray<NSArray<FLEXMethod *> *> *allClassMethods = [NSMutableArray new];
 | |
| 
 | |
|     // Loop over each class and each superclass, collect
 | |
|     // the fresh and unique metadata in each category
 | |
|     Class superclass = nil;
 | |
|     NSInteger count = self.classHierarchyClasses.count;
 | |
|     NSInteger rootIdx = count - 1;
 | |
|     for (NSInteger i = 0; i < count; i++) {
 | |
|         Class cls = self.classHierarchyClasses[i];
 | |
|         superclass = (i < rootIdx) ? self.classHierarchyClasses[i+1] : nil;
 | |
| 
 | |
|         [allProperties addObject:[self
 | |
|             metadataUniquedByName:[cls flex_allInstanceProperties]
 | |
|             superclass:superclass
 | |
|             kind:FLEXMetadataKindProperties
 | |
|             skip:showMethodOverrides
 | |
|         ]];
 | |
|         [allClassProps addObject:[self
 | |
|             metadataUniquedByName:[cls flex_allClassProperties]
 | |
|             superclass:superclass
 | |
|             kind:FLEXMetadataKindClassProperties
 | |
|             skip:showMethodOverrides
 | |
|         ]];
 | |
|         [_allIvars addObject:[self
 | |
|             metadataUniquedByName:[cls flex_allIvars]
 | |
|             superclass:nil
 | |
|             kind:FLEXMetadataKindIvars
 | |
|             skip:NO
 | |
|         ]];
 | |
|         [allMethods addObject:[self
 | |
|             metadataUniquedByName:[cls flex_allInstanceMethods]
 | |
|             superclass:superclass
 | |
|             kind:FLEXMetadataKindMethods
 | |
|             skip:showMethodOverrides
 | |
|         ]];
 | |
|         [allClassMethods addObject:[self
 | |
|             metadataUniquedByName:[cls flex_allClassMethods]
 | |
|             superclass:superclass
 | |
|             kind:FLEXMetadataKindClassMethods
 | |
|             skip:showMethodOverrides
 | |
|         ]];
 | |
|         [_allConformedProtocols addObject:[self
 | |
|             metadataUniquedByName:[cls flex_protocols]
 | |
|             superclass:superclass
 | |
|             kind:FLEXMetadataKindProtocols
 | |
|             skip:NO
 | |
|         ]];
 | |
|         
 | |
|         // TODO: join instance size, image name, and class hierarchy into a single model object
 | |
|         // This would greatly reduce the laziness that has begun to manifest itself here
 | |
|         [_allInstanceSizes addObject:[FLEXStaticMetadata
 | |
|             style:FLEXStaticMetadataRowStyleKeyValue
 | |
|             title:@"Instance Size" number:@(class_getInstanceSize(cls))
 | |
|         ]];
 | |
|         [_allImageNames addObject:[FLEXStaticMetadata
 | |
|             style:FLEXStaticMetadataRowStyleDefault
 | |
|             title:@"Image Name" string:@(class_getImageName(cls) ?: "Created at Runtime")
 | |
|         ]];
 | |
|     }
 | |
|     
 | |
|     _classHierarchy = [FLEXStaticMetadata classHierarchy:self.classHierarchyClasses];
 | |
|     
 | |
|     NSArray<NSArray<FLEXProperty *> *> *properties = allProperties;
 | |
|     
 | |
|     // Potentially filter property-backing ivars
 | |
|     if (hideBackingIvars) {
 | |
|         NSArray<NSArray<FLEXIvar *> *> *ivars = _allIvars.copy;
 | |
|         _allIvars = [ivars flex_mapped:^id(NSArray<FLEXIvar *> *list, NSUInteger idx) {
 | |
|             // Get a set of all backing ivar names for the current class in the hierarchy
 | |
|             NSSet *ivarNames = [NSSet setWithArray:({
 | |
|                 [properties[idx] flex_mapped:^id(FLEXProperty *p, NSUInteger idx) {
 | |
|                     // Nil if no ivar, and array is flatted
 | |
|                     return p.likelyIvarName;
 | |
|                 }];
 | |
|             })];
 | |
|             
 | |
|             // Remove ivars whose name is in the ivar names list
 | |
|             return [list flex_filtered:^BOOL(FLEXIvar *ivar, NSUInteger idx) {
 | |
|                 return ![ivarNames containsObject:ivar.name];
 | |
|             }];
 | |
|         }];
 | |
|     }
 | |
|     
 | |
|     // Potentially filter property-backing methods
 | |
|     if (hidePropertyMethods) {
 | |
|         allMethods = [allMethods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
 | |
|             // Get a set of all property method names for the current class in the hierarchy
 | |
|             NSSet *methodNames = [NSSet setWithArray:({
 | |
|                 [properties[idx] flex_flatmapped:^NSArray *(FLEXProperty *p, NSUInteger idx) {
 | |
|                     if (p.likelyGetterExists) {
 | |
|                         if (p.likelySetterExists) {
 | |
|                             return @[p.likelyGetterString, p.likelySetterString];
 | |
|                         }
 | |
|                         
 | |
|                         return @[p.likelyGetterString];
 | |
|                     } else if (p.likelySetterExists) {
 | |
|                         return @[p.likelySetterString];
 | |
|                     }
 | |
|                     
 | |
|                     return nil;
 | |
|                 }];
 | |
|             })];
 | |
|             
 | |
|             // Remove methods whose name is in the property method names list
 | |
|             return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
 | |
|                 return ![methodNames containsObject:method.selectorString];
 | |
|             }];
 | |
|         }];
 | |
|     }
 | |
|     
 | |
|     if (hidePrivateMethods) {
 | |
|         id methodMapBlock = ^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
 | |
|             // Remove methods which contain an underscore
 | |
|             return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
 | |
|                 return ![method.selectorString containsString:@"_"];
 | |
|             }];
 | |
|         };
 | |
|         id propertyMapBlock = ^id(NSArray<FLEXProperty *> *list, NSUInteger idx) {
 | |
|             // Remove methods which contain an underscore
 | |
|             return [list flex_filtered:^BOOL(FLEXProperty *prop, NSUInteger idx) {
 | |
|                 return ![prop.name containsString:@"_"];
 | |
|             }];
 | |
|         };
 | |
|         
 | |
|         allMethods = [allMethods flex_mapped:methodMapBlock];
 | |
|         allClassMethods = [allClassMethods flex_mapped:methodMapBlock];
 | |
|         allProperties = [allProperties flex_mapped:propertyMapBlock];
 | |
|         allClassProps = [allClassProps flex_mapped:propertyMapBlock];
 | |
|     }
 | |
|     
 | |
|     _allProperties = allProperties;
 | |
|     _allClassProperties = allClassProps;
 | |
|     _allMethods = allMethods;
 | |
|     _allClassMethods = allClassMethods;
 | |
| 
 | |
|     // Set up UIKit helper data
 | |
|     // Really, we only need to call this on properties and ivars
 | |
|     // because no other metadata types support editing.
 | |
|     NSArray<NSArray *>*metadatas = @[
 | |
|         _allProperties, _allClassProperties, _allIvars,
 | |
|        /* _allMethods, _allClassMethods, _allConformedProtocols */
 | |
|     ];
 | |
|     for (NSArray *matrix in metadatas) {
 | |
|         for (NSArray *metadataByClass in matrix) {
 | |
|             [FLEXObjectExplorer configureDefaultsForItems:metadataByClass];
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     [self reloadScopedMetadata];
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - Private
 | |
| 
 | |
| - (void)reloadScopedMetadata {
 | |
|     _properties = self.allProperties[self.classScope];
 | |
|     _classProperties = self.allClassProperties[self.classScope];
 | |
|     _ivars = self.allIvars[self.classScope];
 | |
|     _methods = self.allMethods[self.classScope];
 | |
|     _classMethods = self.allClassMethods[self.classScope];
 | |
|     _conformedProtocols = self.allConformedProtocols[self.classScope];
 | |
|     _instanceSize = self.allInstanceSizes[self.classScope];
 | |
|     _imageName = self.allImageNames[self.classScope];
 | |
| }
 | |
| 
 | |
| /// Accepts an array of flex metadata objects and discards objects
 | |
| /// with duplicate names, as well as properties and methods which
 | |
| /// aren't "new" (i.e. those which the superclass responds to)
 | |
| - (NSArray *)metadataUniquedByName:(NSArray *)list
 | |
|                         superclass:(Class)superclass
 | |
|                               kind:(FLEXMetadataKind)kind
 | |
|                               skip:(BOOL)skipUniquing {
 | |
|     if (skipUniquing) {
 | |
|         return list;
 | |
|     }
 | |
|     
 | |
|     // Remove items with same name and return filtered list
 | |
|     NSMutableSet *names = [NSMutableSet new];
 | |
|     return [list flex_filtered:^BOOL(id obj, NSUInteger idx) {
 | |
|         NSString *name = [obj name];
 | |
|         if ([names containsObject:name]) {
 | |
|             return NO;
 | |
|         } else {
 | |
|             [names addObject:name];
 | |
| 
 | |
|             // Skip methods and properties which are just overrides,
 | |
|             // potentially skip ivars and methods associated with properties
 | |
|             switch (kind) {
 | |
|                 case FLEXMetadataKindProperties:
 | |
|                     if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
 | |
|                         return NO;
 | |
|                     }
 | |
|                     break;
 | |
|                 case FLEXMetadataKindClassProperties:
 | |
|                     if ([superclass respondsToSelector:[obj likelyGetter]]) {
 | |
|                         return NO;
 | |
|                     }
 | |
|                     break;
 | |
|                 case FLEXMetadataKindMethods:
 | |
|                     if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
 | |
|                         return NO;
 | |
|                     }
 | |
|                     break;
 | |
|                 case FLEXMetadataKindClassMethods:
 | |
|                     if ([superclass respondsToSelector:NSSelectorFromString(name)]) {
 | |
|                         return NO;
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 case FLEXMetadataKindProtocols:
 | |
|                 case FLEXMetadataKindClassHierarchy:
 | |
|                 case FLEXMetadataKindOther:
 | |
|                     return YES; // These types are already uniqued
 | |
|                     break;
 | |
|                     
 | |
|                 // Ivars cannot be overidden
 | |
|                 case FLEXMetadataKindIvars: break;
 | |
|             }
 | |
| 
 | |
|             return YES;
 | |
|         }
 | |
|     }];
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - Superclasses
 | |
| 
 | |
| - (void)reloadClassHierarchy {
 | |
|     // The class hierarchy will never contain metaclass objects by this logic;
 | |
|     // it is always the same for a given class and instances of it
 | |
|     _classHierarchyClasses = [[self.object class] flex_classHierarchy];
 | |
| }
 | |
| 
 | |
| @end
 | 
