mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-08-24 11:28:52 -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
|