Преглед изворни кода

Add "Explore <class>" to context menu for props/ivars

Tanner Bennett пре 6 година
родитељ
комит
edf5426e80

+ 11 - 6
Classes/ObjectExplorers/Sections/FLEXMetadataSection.m

@@ -198,7 +198,7 @@
 - (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender {
     NSArray<UIMenuElement *> *existingItems = [super menuItemsForRow:row sender:sender];
     
-    // These two metadata kinds don't need an "Explore Metadata…" option
+    // These two metadata kinds don't any of the additional options below
     switch (self.metadataKind) {
         case FLEXMetadataKindClassHierarchy:
         case FLEXMetadataKindOther:
@@ -207,10 +207,11 @@
         default: break;
     }
     
-    id metadata = self.metadata[row];
+    id<FLEXRuntimeMetadata> metadata = self.metadata[row];
+    NSMutableArray<UIMenuElement *> *menuItems = [NSMutableArray new];
     
-    UIAction *explore = [UIAction
-        actionWithTitle:@"Explore Metadata"
+    [menuItems addObject:[UIAction
+        actionWithTitle:@"Explore Metadata"
         image:nil
         identifier:nil
         handler:^(__kindof UIAction *action) {
@@ -218,9 +219,13 @@
                 explorerViewControllerForObject:metadata
             ] animated:YES];
         }
-    ];
+    ]];
+    [menuItems addObjectsFromArray:[metadata
+        additionalActionsWithTarget:self.explorer.object sender:sender
+    ]];
+    [menuItems addObjectsFromArray:existingItems];
     
-    return [@[explore] arrayByAddingObjectsFromArray:existingItems];
+    return menuItems.copy;
 }
 
 - (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row {

+ 8 - 0
Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.h

@@ -7,6 +7,7 @@
 //
 
 #import <UIKit/UIKit.h>
+#import "FLEXUtility.h"
 #import "FLEXProperty.h"
 #import "FLEXIvar.h"
 #import "FLEXMethod.h"
@@ -37,12 +38,19 @@
 - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object;
 /// Return nil to use the default reuse identifier
 - (NSString *)reuseIdentifierWithTarget:(id)object;
+
+#if FLEX_AT_LEAST_IOS13_SDK
+
+/// An array of actions to place in the first section of the context menu.
+- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender API_AVAILABLE(ios(13.0));
 /// An array where every 2 elements are a key-value pair. The key is a description
 /// of what to copy like "Name" and the values are what will be copied.
 - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object;
 /// Properties and ivars return the address of an object, if they hold one.
 - (NSString *)contextualSubtitleWithTarget:(id)object;
 
+#endif
+
 @end
 
 // Even if a property is readonly, it still may be editable

+ 109 - 23
Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.m

@@ -114,33 +114,64 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
+    Class propertyClass = self.attributes.typeEncoding.flex_typeClass;
+    
+    // "Explore PropertyClass" for properties with a concrete class name
+    if (propertyClass) {
+        NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(propertyClass)];
+        return @[[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
+            UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:propertyClass];
+            [sender.navigationController pushViewController:explorer animated:YES];
+        }]];
+    }
+    
+    return nil;
+}
+
 - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
-    BOOL returnsObject = self.attributes.typeEncoding.typeIsObjectOrClass;
-    id value = [self currentValueBeforeUnboxingWithTarget:object];
-    return @[
-        @"Name",                        self.name ?: @"",
-        @"Type",                        self.attributes.typeEncoding ?: @"",
-        @"Declaration",                 self.fullDescription ?: @"",
-        @"Value Preview",               [self previewWithTarget:object],
-        @"Value Address",               returnsObject ? [FLEXUtility addressOfObject:value] : @"",
-        @"Getter",                      NSStringFromSelector(self.likelyGetter) ?: @"",
-        @"Setter",                      self.likelySetterExists ? NSStringFromSelector(self.likelySetter) : @"",
-        @"Image Name",                  self.imageName ?: @"",
-        @"Attributes",                  self.attributes.string ?: @"",
+    BOOL returnsObject = self.attributes.typeEncoding.flex_typeIsObjectOrClass;
+    BOOL targetNotNil = [self appropriateTargetForPropertyType:object] != nil;
+    
+    NSMutableArray *items = [NSMutableArray arrayWithArray:@[
+        @"Name",                      self.name ?: @"",
+        @"Type",                      self.attributes.typeEncoding ?: @"",
+        @"Declaration",               self.fullDescription ?: @"",
+    ]];
+    
+    if (targetNotNil) {
+        id value = [self currentValueBeforeUnboxingWithTarget:object];
+        [items addObjectsFromArray:@[
+            @"Value Preview",         [self previewWithTarget:object],
+            @"Value Address",         returnsObject ? [FLEXUtility addressOfObject:value] : @"",
+        ]];
+    }
+    
+    [items addObjectsFromArray:@[
+        @"Getter",                    NSStringFromSelector(self.likelyGetter) ?: @"",
+        @"Setter",                    self.likelySetterExists ? NSStringFromSelector(self.likelySetter) : @"",
+        @"Image Name",                self.imageName ?: @"",
+        @"Attributes",                self.attributes.string ?: @"",
         @"objc_property",             [FLEXUtility pointerToString:self.objc_property],
         @"objc_property_attribute_t", [FLEXUtility pointerToString:self.attributes.list],
-    ];
+    ]];
+    
+    return items;
 }
 
 - (NSString *)contextualSubtitleWithTarget:(id)object {
     id target = [self appropriateTargetForPropertyType:object];
-    if (target && self.attributes.typeEncoding.typeIsObjectOrClass) {
+    if (target && self.attributes.typeEncoding.flex_typeIsObjectOrClass) {
         return [FLEXUtility addressOfObject:[self currentValueBeforeUnboxingWithTarget:target]];
     }
     
     return nil;
 }
 
+#endif
+
 @end
 
 
@@ -211,33 +242,64 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
+    Class ivarClass = self.typeEncoding.flex_typeClass;
+    
+    // "Explore PropertyClass" for properties with a concrete class name
+    if (ivarClass) {
+        NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(ivarClass)];
+        return @[[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
+            UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:ivarClass];
+            [sender.navigationController pushViewController:explorer animated:YES];
+        }]];
+    }
+    
+    return nil;
+}
+
 - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
-    BOOL returnsObject = self.typeEncoding.typeIsObjectOrClass;
-    id value = [self getValue:object];
-    return @[
+    BOOL isInstance = !object_isClass(object);
+    BOOL returnsObject = self.typeEncoding.flex_typeIsObjectOrClass;
+    id value = isInstance ? [self getValue:object] : nil;
+    
+    NSMutableArray *items = [NSMutableArray arrayWithArray:@[
         @"Name",          self.name ?: @"",
         @"Type",          self.typeEncoding ?: @"",
         @"Declaration",   self.description ?: @"",
-        @"Value Preview", [self previewWithTarget:object],
-        @"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
+    ]];
+    
+    if (isInstance) {
+        [items addObjectsFromArray:@[
+            @"Value Preview", isInstance ? [self previewWithTarget:object] : @"",
+            @"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
+        ]];
+    }
+    
+    [items addObjectsFromArray:@[
         @"Size",          @(self.size).stringValue,
         @"Offset",        @(self.offset).stringValue,
-        @"objc_ivar",   [FLEXUtility pointerToString:self.objc_ivar],
-    ];
+        @"objc_ivar",     [FLEXUtility pointerToString:self.objc_ivar],
+    ]];
+    
+    return items;
 }
 
 - (NSString *)contextualSubtitleWithTarget:(id)object {
-    if (!object_isClass(object) && self.typeEncoding.typeIsObjectOrClass) {
+    if (!object_isClass(object) && self.typeEncoding.flex_typeIsObjectOrClass) {
         return [FLEXUtility addressOfObject:[self getValue:object]];
     }
     
     return nil;
 }
 
+#endif
+
 @end
 
 
-#pragma mark FLEXMethod*
+#pragma mark FLEXMethod
 @implementation FLEXMethodBase (UIKitHelpers)
 
 - (BOOL)isEditable {
@@ -277,6 +339,12 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
+    return nil;
+}
+
 - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
     return @[
         @"Selector",      self.name ?: @"",
@@ -289,6 +357,8 @@
     return nil;
 }
 
+#endif
+
 @end
 
 @implementation FLEXMethod (UIKitHelpers)
@@ -363,6 +433,12 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
+    return nil;
+}
+
 - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
     NSArray<NSString *> *conformanceNames = [self.protocols valueForKeyPath:@"name"];
     NSString *conformances = [conformanceNames componentsJoinedByString:@"\n"];
@@ -376,6 +452,8 @@
     return nil;
 }
 
+#endif
+
 @end
 
 
@@ -463,6 +541,12 @@
     return UITableViewCellAccessoryNone;
 }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
+    return nil;
+}
+
 - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
     return @[self.name, self.subtitle];
 }
@@ -471,6 +555,8 @@
     return nil;
 }
 
+#endif
+
 @end
 
 

+ 6 - 4
Classes/Utility/Categories/NSString+FLEX.h

@@ -12,13 +12,15 @@
 @interface NSString (FLEXTypeEncoding)
 
 ///@return whether this type starts with the const specifier
-@property (nonatomic, readonly) BOOL typeIsConst;
+@property (nonatomic, readonly) BOOL flex_typeIsConst;
 /// @return the first char in the type encoding that is not the const specifier
-@property (nonatomic, readonly) FLEXTypeEncoding firstNonConstType;
+@property (nonatomic, readonly) FLEXTypeEncoding flex_firstNonConstType;
 /// @return whether this type is an objc object of any kind, even if it's const
-@property (nonatomic, readonly) BOOL typeIsObjectOrClass;
+@property (nonatomic, readonly) BOOL flex_typeIsObjectOrClass;
+/// @return the class named in this type encoding if it is of the form \c @"MYClass"
+@property (nonatomic, readonly) Class flex_typeClass;
 /// Includes C strings and selectors as well as regular pointers
-@property (nonatomic, readonly) BOOL typeIsNonObjcPointer;
+@property (nonatomic, readonly) BOOL flex_typeIsNonObjcPointer;
 
 @end
 

+ 46 - 7
Classes/Utility/Categories/NSString+FLEX.m

@@ -53,21 +53,60 @@
 
 @implementation NSString (FLEXTypeEncoding)
 
-- (BOOL)typeIsConst {
+- (NSCharacterSet *)flex_classNameAllowedCharactersSet {
+    static NSCharacterSet *classNameAllowedCharactersSet = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        NSMutableCharacterSet *temp = NSMutableCharacterSet.alphanumericCharacterSet;
+        [temp addCharactersInString:@"_"];
+        classNameAllowedCharactersSet = temp.copy;
+    });
+    
+    return classNameAllowedCharactersSet;
+}
+
+- (BOOL)flex_typeIsConst {
     return [self characterAtIndex:0] == FLEXTypeEncodingConst;
 }
 
-- (FLEXTypeEncoding)firstNonConstType {
-    return [self characterAtIndex:(self.typeIsConst ? 1 : 0)];
+- (FLEXTypeEncoding)flex_firstNonConstType {
+    return [self characterAtIndex:(self.flex_typeIsConst ? 1 : 0)];
 }
 
-- (BOOL)typeIsObjectOrClass {
-    FLEXTypeEncoding type = self.firstNonConstType;
+- (BOOL)flex_typeIsObjectOrClass {
+    FLEXTypeEncoding type = self.flex_firstNonConstType;
     return type == FLEXTypeEncodingObjcObject || type == FLEXTypeEncodingObjcClass;
 }
 
-- (BOOL)typeIsNonObjcPointer {
-    FLEXTypeEncoding type = self.firstNonConstType;
+- (Class)flex_typeClass {
+    if (!self.flex_typeIsObjectOrClass) {
+        return nil;
+    }
+    
+    NSScanner *scan = [NSScanner scannerWithString:self];
+    // Skip const
+    [scan scanString:@"r" intoString:nil];
+    // Scan leading @"
+    if (![scan scanString:@"@\"" intoString:nil]) {
+        return nil;
+    }
+    
+    // Scan class name
+    NSString *name = nil;
+    if (![scan scanCharactersFromSet:self.flex_classNameAllowedCharactersSet intoString:&name]) {
+        return nil;
+    }
+    // Scan trailing quote
+    if (![scan scanString:@"\"" intoString:nil]) {
+        return nil;
+    }
+    
+    // Return found class
+    return NSClassFromString(name);
+}
+
+- (BOOL)flex_typeIsNonObjcPointer {
+    FLEXTypeEncoding type = self.flex_firstNonConstType;
     return type == FLEXTypeEncodingPointer ||
            type == FLEXTypeEncodingCString ||
            type == FLEXTypeEncodingSelector;