Prechádzať zdrojové kódy

Add FLEXObjectExplorer

This class will become the data source for object explorer view controllers.
Tanner Bennett 6 rokov pred
rodič
commit
ba1de91f85

+ 55 - 0
Classes/ObjectExplorers/FLEXObjectExplorer.h

@@ -0,0 +1,55 @@
+//
+//  FLEXObjectExplorer.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXProperty.h"
+#import "FLEXIvar.h"
+#import "FLEXMethod.h"
+
+@interface FLEXObjectExplorer : NSObject
+
++ (instancetype)forObject:(id)objectOrClass;
+
+@property (nonatomic, readonly) id object;
+/// Subclasses can override to provide a more useful description
+@property (nonatomic, readonly) NSString *objectDescription;
+
+/// @return \c YES if \c object is an instance of a class,
+/// or \c NO if \c object is a class itself.
+@property (nonatomic, readonly) BOOL objectIsInstance;
+
+/// An index into the `classHierarchy` array.
+///
+/// This property determines which set of data comes out of the metadata arrays below
+/// For example, \c properties contains the properties of the selected class scope,
+/// while \c allProperties is an array of arrays where each array is a set of
+/// properties for a class in the class hierarchy of the current object.
+@property (nonatomic) NSInteger classScope;
+
+@property (nonatomic, readonly) NSArray<NSArray<FLEXProperty *> *> *allProperties;
+@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
+
+@property (nonatomic, readonly) NSArray<NSArray<FLEXIvar *> *> *allIvars;
+@property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
+
+@property (nonatomic, readonly) NSArray<NSArray<FLEXMethod *> *> *allMethods;
+@property (nonatomic, readonly) NSArray<FLEXMethod *> *methods;
+
+@property (nonatomic, readonly) NSArray<NSArray<FLEXMethod *> *> *allClassMethods;
+@property (nonatomic, readonly) NSArray<FLEXMethod *> *classMethods;
+
+@property (nonatomic, readonly) NSArray<Class> *classHierarchy;
+@property (nonatomic, readonly) NSArray<Class> *filteredSuperclasses;
+
+- (void)reloadMetadata;
+- (void)reloadClassHierarchy;
+
+- (id)valueForProperty:(FLEXProperty *)property;
+- (id)valueForIvar:(FLEXIvar *)ivar;
+
+@end

+ 232 - 0
Classes/ObjectExplorers/FLEXObjectExplorer.m

@@ -0,0 +1,232 @@
+//
+//  FLEXObjectExplorer.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXObjectExplorer.h"
+#import "FLEXUtility.h"
+#import "FLEXRuntimeUtility.h"
+#import "NSObject+Reflection.h"
+#import "FLEXRuntime+Compare.h"
+#import "FLEXRuntime+UIKitHelpers.h"
+#import "FLEXPropertyAttributes.h"
+#import "NSObject+Reflection.h"
+#import "FLEXMetadataSection.h"
+
+@interface FLEXObjectExplorer () {
+    NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
+    NSMutableArray<NSArray<FLEXIvar *> *> *_allIvars;
+    NSMutableArray<NSArray<FLEXMethod *> *> *_allMethods;
+    NSMutableArray<NSArray<FLEXMethod *> *> *_allClassMethods;
+}
+@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
+
+- (NSString *)objectDescription {
+    // Hard-code UIColor description
+    if ([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];
+    }
+
+    return description;
+}
+
+- (void)setClassScope:(NSInteger)classScope {
+    _classScope = classScope;
+    
+    [self reloadScopedMetadata];
+}
+
+- (void)reloadMetadata {
+    _allProperties = [NSMutableArray new];
+    _allIvars = [NSMutableArray new];
+    _allMethods = [NSMutableArray new];
+    _allClassMethods = [NSMutableArray new];
+
+    [self reloadClassHierarchy];
+
+    // Loop over each class and each superclass, collect
+    // the fresh and unique metadata in each category
+    Class superclass = nil;
+    NSInteger count = self.classHierarchy.count;
+    NSInteger rootIdx = count - 1;
+    for (NSInteger i = 0; i < count; i++) {
+        Class cls = self.classHierarchy[i];
+        superclass = (i < rootIdx) ? self.classHierarchy[i+1] : nil;
+
+        [_allProperties addObject:[self
+            metadataUniquedByName:[cls flex_allInstanceProperties]
+            superclass:superclass
+            kind:FLEXMetadataKindProperties
+        ]];
+        [_allIvars addObject:[self
+            metadataUniquedByName:[cls flex_allIvars]
+            superclass:nil
+            kind:FLEXMetadataKindIvars
+        ]];
+        [_allMethods addObject:[self
+            metadataUniquedByName:[cls flex_allInstanceMethods]
+            superclass:superclass
+            kind:FLEXMetadataKindMethods
+        ]];
+        [_allClassMethods addObject:[self
+            metadataUniquedByName:[cls flex_allClassMethods]
+            superclass:superclass
+            kind:FLEXMetadataKindClassMethods
+        ]];
+    }
+
+    // Set up UIKit helper data
+    for (NSArray *matrix in @[_allProperties, _allIvars, _allMethods, _allClassMethods]) {
+        for (NSArray *metadataByClass in matrix) {
+            for (id<FLEXRuntimeMetadata> metadata in metadataByClass) {
+                metadata.tag = metadata.isEditable ? @YES : nil;
+            }
+        }
+    }
+    
+    [self reloadScopedMetadata];
+}
+
+
+#pragma mark - Private
+
+- (void)reloadScopedMetadata {
+    _properties = self.allProperties[self.classScope];
+    _ivars = self.allIvars[self.classScope];
+    _methods = self.allMethods[self.classScope];
+    _classMethods = self.allClassMethods[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 {
+    // 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 nil;
+        } else {
+            [names addObject:name];
+
+            // Skip methods and properties which are just overrides
+            switch (kind) {
+                case FLEXMetadataKindProperties:
+                    if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
+                        return nil;
+                    }
+                    break;
+//                case FLEXMetadataKindClassProperties:
+//                    if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
+//                        return nil;
+//                    }
+//                    break;
+                case FLEXMetadataKindMethods:
+                    if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
+                        return nil;
+                    }
+                    break;
+                case FLEXMetadataKindClassMethods:
+                    if ([superclass respondsToSelector:NSSelectorFromString(name)]) {
+                        return nil;
+                    }
+                    break;
+                    
+                // Ivars cannot be overidden
+                case FLEXMetadataKindIvars: break;
+            }
+
+            return obj;
+        }
+    }];
+}
+
+
+#pragma mark Values
+
+- (id)valueForProperty:(FLEXProperty *)property {
+    if (self.objectIsInstance) {
+        return [property getPotentiallyUnboxedValue:self.object];
+    }
+    
+    return nil;
+}
+
+- (id)valueForIvar:(FLEXIvar *)ivar {
+    if (self.objectIsInstance) {
+        return [ivar getPotentiallyUnboxedValue:self.object];
+    }
+    
+    return nil;
+}
+
+
+#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
+    _classHierarchy = [[self.object class] flex_classHierarchy];
+}
+
+//- (void)updateFilteredSuperclasses {
+//    if (self.filterText.length > 0) {
+//        NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
+//        for (Class superclass in self.classHierarchy) {
+//            if ([NSStringFromClass(superclass) localizedCaseInsensitiveContainsString:self.filterText]) {
+//                [filteredSuperclasses addObject:superclass];
+//            }
+//        }
+//        _filteredSuperclasses = filteredSuperclasses;
+//    } else {
+//        _filteredSuperclasses = self.classHierarchy;
+//    }
+//}
+
+
+@end

+ 4 - 0
Classes/Utility/Categories/NSObject+Reflection.h

@@ -42,6 +42,10 @@ extern NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger coun
 /// @return The old superclass.
 /// @return The old superclass.
 + (Class)flex_setSuperclass:(Class)superclass;
 + (Class)flex_setSuperclass:(Class)superclass;
 
 
+/// @return a list of classes going up the class hierarchy,
+/// starting with the receiver and ending with the root class.
++ (NSArray<Class> *)flex_classHierarchy;
+
 @end
 @end
 
 
 
 

+ 13 - 7
Classes/Utility/Categories/NSObject+Reflection.m

@@ -84,16 +84,22 @@ NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...)
     return class_getInstanceSize(self.class);
     return class_getInstanceSize(self.class);
 }
 }
 
 
-#ifdef __clang__
-#pragma clang diagnostic push
-#endif
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 + (Class)flex_setSuperclass:(Class)superclass {
 + (Class)flex_setSuperclass:(Class)superclass {
+    #pragma clang diagnostic push
+    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
     return class_setSuperclass(self, superclass);
     return class_setSuperclass(self, superclass);
+    #pragma clang diagnostic pop
+}
+
++ (NSArray<Class> *)flex_classHierarchy {
+    NSMutableArray *classes = [NSMutableArray array];
+    Class cls = self;
+    do {
+        [classes addObject:cls];
+    } while ((cls = [cls superclass]));
+
+    return classes.copy;
 }
 }
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
 
 
 @end
 @end
 
 

+ 8 - 0
FLEX.xcodeproj/project.pbxproj

@@ -171,6 +171,8 @@
 		94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */; };
 		94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */; };
 		94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
 		C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
+		C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */ = {isa = PBXBuildFile; fileRef = C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */; };
+		C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */ = {isa = PBXBuildFile; fileRef = C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */; };
 		C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */; };
 		C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */; };
 		C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */; };
 		C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */; };
 		C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */; };
 		C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */; };
@@ -420,6 +422,8 @@
 		94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardHelpViewController.h; sourceTree = "<group>"; };
 		94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardHelpViewController.h; sourceTree = "<group>"; };
 		94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeyboardHelpViewController.m; sourceTree = "<group>"; };
 		94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeyboardHelpViewController.m; sourceTree = "<group>"; };
 		C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
 		C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
+		C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FLEXObjectExplorer.h; path = Classes/ObjectExplorers/FLEXObjectExplorer.h; sourceTree = SOURCE_ROOT; };
+		C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FLEXObjectExplorer.m; path = Classes/ObjectExplorers/FLEXObjectExplorer.m; sourceTree = SOURCE_ROOT; };
 		C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXASLLogController.h; sourceTree = "<group>"; };
 		C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXASLLogController.h; sourceTree = "<group>"; };
 		C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXASLLogController.m; sourceTree = "<group>"; };
 		C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXASLLogController.m; sourceTree = "<group>"; };
 		C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBundleExplorerViewController.h; sourceTree = "<group>"; };
 		C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBundleExplorerViewController.h; sourceTree = "<group>"; };
@@ -572,6 +576,8 @@
 		3A4C943B1B5B21410088C3F2 /* ObjectExplorers */ = {
 		3A4C943B1B5B21410088C3F2 /* ObjectExplorers */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */,
+				C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */,
 				3A4C944A1B5B21410088C3F2 /* FLEXObjectExplorerFactory.h */,
 				3A4C944A1B5B21410088C3F2 /* FLEXObjectExplorerFactory.h */,
 				3A4C944B1B5B21410088C3F2 /* FLEXObjectExplorerFactory.m */,
 				3A4C944B1B5B21410088C3F2 /* FLEXObjectExplorerFactory.m */,
 				C3F31D3A2267D883003C991A /* Views */,
 				C3F31D3A2267D883003C991A /* Views */,
@@ -1065,6 +1071,7 @@
 				3A4C94E91B5B21410088C3F2 /* FLEXHierarchyTableViewController.h in Headers */,
 				3A4C94E91B5B21410088C3F2 /* FLEXHierarchyTableViewController.h in Headers */,
 				3A4C94F31B5B21410088C3F2 /* FLEXArgumentInputFontView.h in Headers */,
 				3A4C94F31B5B21410088C3F2 /* FLEXArgumentInputFontView.h in Headers */,
 				3A4C95261B5B21410088C3F2 /* FLEXGlobalsTableViewController.h in Headers */,
 				3A4C95261B5B21410088C3F2 /* FLEXGlobalsTableViewController.h in Headers */,
+				C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */,
 				224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
 				224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
 				7349FD6A22B93CDF00051810 /* FLEXColor.h in Headers */,
 				7349FD6A22B93CDF00051810 /* FLEXColor.h in Headers */,
 				C36FBFD3230F3B98008D95D5 /* FLEXMethod.h in Headers */,
 				C36FBFD3230F3B98008D95D5 /* FLEXMethod.h in Headers */,
@@ -1279,6 +1286,7 @@
 				3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
 				3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
 				C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
 				C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
 				94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,
 				94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,
+				C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */,
 				3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
 				3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
 				779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
 				779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
 				C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */,
 				C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */,