Browse Source

Merge branch 'master' of https://github.com/FLEXTool/FLEX

# Conflicts:
#	Classes/Utility/Categories/UIView+FLEX_Layout.h
#	FLEX.xcodeproj/project.pbxproj
Kevin Bradley 3 years ago
parent
commit
18637b70ab
38 changed files with 274 additions and 91 deletions
  1. 27 0
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 10 0
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 6 3
      Classes/Core/Controllers/FLEXNavigationController.m
  4. 1 1
      Classes/Core/Views/Carousel/FLEXCarouselCell.m
  5. 1 1
      Classes/Core/Views/Carousel/FLEXScopeCarousel.m
  6. 0 5
      Classes/FLEX-Categories.h
  7. 46 14
      Classes/GlobalStateExplorers/FLEXObjectListViewController.m
  8. 1 1
      Classes/GlobalStateExplorers/FLEXObjectRef.m
  9. 2 2
      Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/FLEXRuntimeClient.m
  10. 3 3
      Classes/GlobalStateExplorers/RuntimeBrowser/FLEXKeyPathSearchController.m
  11. 1 1
      Classes/ObjectExplorers/FLEXObjectExplorerViewController.m
  12. 2 2
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.m
  13. 1 1
      Classes/Utility/Categories/NSArray+FLEX.h
  14. 1 1
      Classes/Utility/Categories/NSArray+FLEX.m
  15. 1 1
      Classes/Utility/Categories/NSTimer+FLEX.h
  16. 1 1
      Classes/Utility/Categories/NSTimer+FLEX.m
  17. 0 0
      Classes/Utility/Categories/Private/NSDictionary+ObjcRuntime.h
  18. 0 0
      Classes/Utility/Categories/Private/NSDictionary+ObjcRuntime.m
  19. 0 0
      Classes/Utility/Categories/Private/NSMapTable+FLEX_Subscripting.h
  20. 0 0
      Classes/Utility/Categories/Private/NSMapTable+FLEX_Subscripting.m
  21. 2 2
      Classes/Utility/Categories/NSString+FLEX.h
  22. 2 2
      Classes/Utility/Categories/NSString+FLEX.m
  23. 0 0
      Classes/Utility/Categories/Private/NSString+ObjcRuntime.h
  24. 0 0
      Classes/Utility/Categories/Private/NSString+ObjcRuntime.m
  25. 23 0
      Classes/Utility/Categories/Private/UIView+FLEX_Layout.h
  26. 9 9
      Classes/Utility/Categories/UIView+FLEX_Layout.m
  27. 2 2
      Classes/Utility/Categories/UIGestureRecognizer+Blocks.h
  28. 5 5
      Classes/Utility/Categories/UIGestureRecognizer+Blocks.m
  29. 1 1
      Classes/Utility/Categories/UITextField+Range.h
  30. 1 1
      Classes/Utility/Categories/UITextField+Range.m
  31. 0 1
      Classes/Utility/FLEXUtility.h
  32. 1 0
      Classes/Utility/Runtime/FLEXRuntimeUtility.h
  33. 21 15
      Classes/Utility/Runtime/FLEXRuntimeUtility.m
  34. 1 0
      Classes/ViewHierarchy/TreeExplorer/FLEXHierarchyTableViewController.m
  35. 38 15
      FLEX.xcodeproj/project.pbxproj
  36. 20 1
      FLEXTests/FLEXTests.m
  37. 19 0
      FLEXTests/Supporting Files/FLEXNewRootClass.h
  38. 25 0
      FLEXTests/Supporting Files/FLEXNewRootClass.m

+ 27 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -0,0 +1,27 @@
+---
+name: Bug report
+about: Report a bug in FLEX
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+### Environment
+- Platform+version: **iOS 14** <!--- Change to match your platform and version -->
+- FLEX version: **9.9.9** <!--- Change to the version of FLEX you're using -->
+<!--- FLEXing / libFLEX users: please include FLEXing and libFLEX versions separately -->
+
+### Bug Report
+
+Here, you can provide a description of the bug. Some tips:
+
+- Please do not paste an entire crash log. Upload the crash log to something like [ghostbin.co](https://ghostbin.co/) or another paste service. Alternatively, you can cut out the relevant stack trace and paste that inside a ` ```code block``` `
+- If the bug is more complex than "this button is broken" or a crash, consider including a sample project. For example, if your app's requests aren't showing up in the network history page.
+- Providing steps to reproduce is always helpful!
+- If you want to include a screenshot or GIF, consider modifying the default markdown for uploaded images to use this code to make the image smaller on desktop:
+  ```
+  <img width="50%" src=your-image-url >
+  ```
+
+This template is a suggestion. You may format your issue however you want, but generally you should at least include your iOS version and FLEX version.

+ 10 - 0
.github/ISSUE_TEMPLATE/feature_request.md

@@ -0,0 +1,10 @@
+---
+name: Feature request
+about: Suggest a new feature for FLEX
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+

+ 6 - 3
Classes/Core/Controllers/FLEXNavigationController.m

@@ -38,10 +38,13 @@
     self.waitingToAddTab = YES;
     
     // Add gesture to reveal toolbar if hidden
-    self.navigationBar.userInteractionEnabled = YES;
-    [self.navigationBar addGestureRecognizer:[[UITapGestureRecognizer alloc]
+    UITapGestureRecognizer *navbarTapGesture = [[UITapGestureRecognizer alloc]
         initWithTarget:self action:@selector(handleNavigationBarTap:)
-    ]];
+    ];
+    
+    // Don't cancel touches to work around bug on versions of iOS prior to 13
+    navbarTapGesture.cancelsTouchesInView = NO;
+    [self.navigationBar addGestureRecognizer:navbarTapGesture];
     
     // Add gesture to dismiss if not presented with a sheet style
     if (@available(iOS 13, *)) {

+ 1 - 1
Classes/Core/Views/Carousel/FLEXCarouselCell.m

@@ -77,7 +77,7 @@
     self.selectionIndicatorStripe.translatesAutoresizingMaskIntoConstraints = NO;
 
     UIView *superview = self.contentView;
-    [self.titleLabel pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)];
+    [self.titleLabel flex_pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)];
 
     [self.selectionIndicatorStripe.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor].active = YES;
     [self.selectionIndicatorStripe.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor].active = YES;

+ 1 - 1
Classes/Core/Views/Carousel/FLEXScopeCarousel.m

@@ -118,7 +118,7 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
 - (void)updateConstraints {
     if (!self.constraintsInstalled) {
         self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
-        [self.collectionView pinEdgesToSuperview];
+        [self.collectionView flex_pinEdgesToSuperview];
         
         self.constraintsInstalled = YES;
     }

+ 0 - 5
Classes/FLEX-Categories.h

@@ -12,16 +12,11 @@
 #import <FLEX/CALayer+FLEX.h>
 #import <FLEX/UIFont+FLEX.h>
 #import <FLEX/UIGestureRecognizer+Blocks.h>
-#import <FLEX/UIView+FLEX_Layout.h>
 #import <FLEX/UIPasteboard+FLEX.h>
 #import <FLEX/UIMenu+FLEX.h>
 #import <FLEX/UITextField+Range.h>
 
 #import <FLEX/NSObject+FLEX_Reflection.h>
 #import <FLEX/NSArray+FLEX.h>
-#import <FLEX/NSDictionary+ObjcRuntime.h>
-#import <FLEX/NSString+ObjcRuntime.h>
-#import <FLEX/NSString+FLEX.h>
 #import <FLEX/NSUserDefaults+FLEX.h>
-#import <FLEX/NSMapTable+FLEX_Subscripting.h>
 #import <FLEX/NSTimer+FLEX.h>

+ 46 - 14
Classes/GlobalStateExplorers/FLEXObjectListViewController.m

@@ -20,6 +20,28 @@
 #import <malloc/malloc.h>
 
 
+typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
+    FLEXObjectReferenceSectionMain,
+    FLEXObjectReferenceSectionAutoLayout,
+    FLEXObjectReferenceSectionKVO,
+    FLEXObjectReferenceSectionFLEX,
+    
+    FLEXObjectReferenceSectionCount
+};
+
+NSArray<NSString *> * FLEXObjectReferenceSectionTitles() {
+    return @[
+        @"", @"AutoLayout", @"Key-Value Observing", @"FLEX"
+    ];
+}
+
+NSString const * FLEXTitleForObjectReferenceSection(FLEXObjectReferenceSection section) {
+    switch (section) {
+        case FLEXObjectReferenceSectionCount: @throw NSInternalInconsistencyException;
+        default: return FLEXObjectReferenceSectionTitles()[section];
+    }
+}
+
 @interface FLEXObjectListViewController ()
 @property (nonatomic, copy) NSArray<FLEXMutableListSection *> *sections;
 @property (nonatomic, copy) NSArray<FLEXMutableListSection *> *allSections;
@@ -38,7 +60,7 @@
 + (NSPredicate *)defaultPredicateForSection:(NSInteger)section {
     // These are the types of references that we typically don't care about.
     // We want this list of "object-ivar pairs" split into two sections.
-    BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
+    BOOL(^isKVORelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
         NSString *row = ref.reference;
         return [row isEqualToString:@"__NSObserver object"] ||
                [row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
@@ -65,28 +87,38 @@
                ([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
                [ignored containsObject:row];
     };
+    
+    /// These are FLEX classes and usually you aren't looking for FLEX references inside FLEX itself
+    BOOL(^isFLEXClass)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
+        return [ref.reference hasPrefix:@"FLEX"];
+    };
 
     BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
-        return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
+        return !(
+            isKVORelated(ref, bindings) ||
+            isConstraintRelated(ref, bindings) ||
+            isFLEXClass(ref, bindings)
+        );
     };
 
     switch (section) {
-        case 0: return [NSPredicate predicateWithBlock:isEssential];
-        case 1: return [NSPredicate predicateWithBlock:isConstraintRelated];
-        case 2: return [NSPredicate predicateWithBlock:isObserver];
+        case FLEXObjectReferenceSectionMain:
+            return [NSPredicate predicateWithBlock:isEssential];
+        case FLEXObjectReferenceSectionAutoLayout:
+            return [NSPredicate predicateWithBlock:isConstraintRelated];
+        case FLEXObjectReferenceSectionKVO:
+            return [NSPredicate predicateWithBlock:isKVORelated];
+        case FLEXObjectReferenceSectionFLEX:
+            return [NSPredicate predicateWithBlock:isFLEXClass];
 
         default: return nil;
     }
 }
 
 + (NSArray<NSPredicate *> *)defaultPredicates {
-    return @[[self defaultPredicateForSection:0],
-             [self defaultPredicateForSection:1],
-             [self defaultPredicateForSection:2]];
-}
-
-+ (NSArray<NSString *> *)defaultSectionTitles {
-    return @[@"", @"AutoLayout", @"Trivial"];
+    return [NSArray flex_forEachUpTo:FLEXObjectReferenceSectionCount map:^id(NSUInteger i) {
+        return [self defaultPredicateForSection:i];
+    }];
 }
 
 
@@ -189,14 +221,14 @@
     }];
 
     NSArray<NSPredicate *> *predicates = [self defaultPredicates];
-    NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
+    NSArray<NSString *> *sectionTitles = FLEXObjectReferenceSectionTitles();
     FLEXObjectListViewController *viewController = [[self alloc]
         initWithReferences:instances
         predicates:predicates
         sectionTitles:sectionTitles
     ];
     viewController.title = [NSString stringWithFormat:@"Referencing %@ %p",
-        NSStringFromClass(object_getClass(object)), object
+        [FLEXRuntimeUtility safeClassNameForObject:object], object
     ];
     return viewController;
 }

+ 1 - 1
Classes/GlobalStateExplorers/FLEXObjectRef.m

@@ -47,7 +47,7 @@
         _object = object;
         _wantsSummary = showSummary;
 
-        NSString *class = NSStringFromClass(object_getClass(object));
+        NSString *class = [FLEXRuntimeUtility safeClassNameForObject:object];
         if (ivar) {
             _reference = [NSString stringWithFormat:@"%@ %@", class, ivar];
         } else if (showSummary) {

+ 2 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/FLEXRuntimeClient.m

@@ -303,14 +303,14 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
         if (options == TBWildcardOptionsAny) {
             return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
                 return [self classNamesInImageAtPath:bundlePath];
-            }] sortedUsingSelector:@selector(caseInsensitiveCompare:)];
+            }] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
         }
 
         return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
             return [[self classNamesInImageAtPath:bundlePath] flex_mapped:^id(NSString *className, NSUInteger idx) {
                 return TBWildcardMap(query, className, options);
             }];
-        }] sortedUsingSelector:@selector(caseInsensitiveCompare:)];
+        }] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
     }
 }
 

+ 3 - 3
Classes/GlobalStateExplorers/RuntimeBrowser/FLEXKeyPathSearchController.m

@@ -101,7 +101,7 @@
 
     // Change "Bundle.fooba" to "Bundle.foobar."
     NSString *orig = self.delegate.searchController.searchBar.text;
-    NSString *keyPath = [orig stringByReplacingLastKeyPathComponent:text];
+    NSString *keyPath = [orig flex_stringByReplacingLastKeyPathComponent:text];
     self.delegate.searchController.searchBar.text = keyPath;
 
     self.keyPath = [FLEXRuntimeKeyPathTokenizer tokenizeString:keyPath];
@@ -130,7 +130,7 @@
     // Available since at least iOS 9, still present in iOS 13
     UITextField *field = [searchBar valueForKey:@"_searchBarTextField"];
 
-    if ([self searchBar:searchBar shouldChangeTextInRange:field.selectedRange replacementText:text]) {
+    if ([self searchBar:searchBar shouldChangeTextInRange:field.flex_selectedRange replacementText:text]) {
         [field replaceRange:field.selectedTextRange withText:text];
     }
 }
@@ -266,7 +266,7 @@
             self.filteredClasses = nil;
         }
 
-        self.timer = [NSTimer fireSecondsFromNow:0.15 block:^{
+        self.timer = [NSTimer flex_fireSecondsFromNow:0.15 block:^{
             [self updateTable];
         }];
     }

+ 1 - 1
Classes/ObjectExplorers/FLEXObjectExplorerViewController.m

@@ -90,7 +90,7 @@
 
     // Use [object class] here rather than object_getClass
     // to avoid the KVO prefix for observed objects
-    self.title = [[self.object class] description];
+    self.title = [FLEXRuntimeUtility safeClassNameForObject:self.object];
 
     // Search
     self.showsSearchBar = YES;

+ 2 - 2
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.m

@@ -377,8 +377,6 @@ typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> Re
                 [bucket addObjectsFromArray:items];
             }
         }
-        
-        [self reset];
     }
 }
 
@@ -480,6 +478,8 @@ typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> Re
             }];
             [self _register:items to:ivarBucket class:cls];
         }
+        
+        [self reset];
     };
 }
 

+ 1 - 1
Classes/Utility/Categories/NSArray+FLEX.h

@@ -27,6 +27,6 @@
 + (instancetype)flex_forEachUpTo:(NSUInteger)bound map:(T(^)(NSUInteger i))block;
 + (instancetype)flex_mapped:(id<NSFastEnumeration>)collection block:(id(^)(T obj, NSUInteger idx))mapFunc;
 
-- (instancetype)sortedUsingSelector:(SEL)selector;
+- (instancetype)flex_sortedUsingSelector:(SEL)selector;
 
 @end

+ 1 - 1
Classes/Utility/Categories/NSArray+FLEX.m

@@ -104,7 +104,7 @@
     return array;
 }
 
-- (instancetype)sortedUsingSelector:(SEL)selector {
+- (instancetype)flex_sortedUsingSelector:(SEL)selector {
     if (FLEXArrayClassIsMutable(self)) {
         NSMutableArray *me = (id)self;
         [me sortUsingSelector:selector];

+ 1 - 1
Classes/Utility/Categories/NSTimer+FLEX.h

@@ -11,7 +11,7 @@ typedef void (^VoidBlock)(void);
 
 @interface NSTimer (Blocks)
 
-+ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
++ (instancetype)flex_fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
 
 // Forward declaration
 //+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

+ 1 - 1
Classes/Utility/Categories/NSTimer+FLEX.m

@@ -14,7 +14,7 @@
 #pragma clang diagnostic ignored "-Wincomplete-implementation"
 @implementation NSTimer (Blocks)
 
-+ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block {
++ (instancetype)flex_fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block {
     if (@available(iOS 10, *)) {
         return [self scheduledTimerWithTimeInterval:delay repeats:NO block:(id)block];
     } else {

Classes/Utility/Categories/NSDictionary+ObjcRuntime.h → Classes/Utility/Categories/Private/NSDictionary+ObjcRuntime.h


Classes/Utility/Categories/NSDictionary+ObjcRuntime.m → Classes/Utility/Categories/Private/NSDictionary+ObjcRuntime.m


Classes/Utility/Categories/NSMapTable+FLEX_Subscripting.h → Classes/Utility/Categories/Private/NSMapTable+FLEX_Subscripting.h


Classes/Utility/Categories/NSMapTable+FLEX_Subscripting.m → Classes/Utility/Categories/Private/NSMapTable+FLEX_Subscripting.m


+ 2 - 2
Classes/Utility/Categories/NSString+FLEX.h

@@ -27,7 +27,7 @@
 
 @interface NSString (KeyPaths)
 
-- (NSString *)stringByRemovingLastKeyPathComponent;
-- (NSString *)stringByReplacingLastKeyPathComponent:(NSString *)replacement;
+- (NSString *)flex_stringByRemovingLastKeyPathComponent;
+- (NSString *)flex_stringByReplacingLastKeyPathComponent:(NSString *)replacement;
 
 @end

+ 2 - 2
Classes/Utility/Categories/NSString+FLEX.m

@@ -128,7 +128,7 @@
 
 @implementation NSString (KeyPaths)
 
-- (NSString *)stringByRemovingLastKeyPathComponent {
+- (NSString *)flex_stringByRemovingLastKeyPathComponent {
     if (![self containsString:@"."]) {
         return @"";
     }
@@ -138,7 +138,7 @@
     return mself;
 }
 
-- (NSString *)stringByReplacingLastKeyPathComponent:(NSString *)replacement {
+- (NSString *)flex_stringByReplacingLastKeyPathComponent:(NSString *)replacement {
     // replacement should not have any escaped '.' in it,
     // so we escape all '.'
     if ([replacement containsString:@"."]) {

Classes/Utility/Categories/NSString+ObjcRuntime.h → Classes/Utility/Categories/Private/NSString+ObjcRuntime.h


Classes/Utility/Categories/NSString+ObjcRuntime.m → Classes/Utility/Categories/Private/NSString+ObjcRuntime.m


+ 23 - 0
Classes/Utility/Categories/Private/UIView+FLEX_Layout.h

@@ -0,0 +1,23 @@
+//
+//  UIView+FLEX_Layout.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/18/19.
+//  Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#define Padding(p) UIEdgeInsetsMake(p, p, p, p)
+
+@interface UIView (FLEX_Layout)
+
+- (void)flex_centerInView:(UIView *)view;
+- (void)flex_pinEdgesTo:(UIView *)view;
+- (void)flex_pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)insets;
+- (void)flex_pinEdgesToSuperview;
+- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets;
+- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets aboveView:(UIView *)sibling;
+- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets belowView:(UIView *)sibling;
+
+@end

+ 9 - 9
Classes/Utility/Categories/UIView+FLEX_Layout.m

@@ -26,14 +26,14 @@
 
 @implementation UIView (FLEX_Layout)
 
-- (void)centerInView:(UIView *)view {
+- (void)flex_centerInView:(UIView *)view {
     [NSLayoutConstraint activateConstraints:@[
         [self.centerXAnchor constraintEqualToAnchor:view.centerXAnchor],
         [self.centerYAnchor constraintEqualToAnchor:view.centerYAnchor],
     ]];
 }
 
-- (void)pinEdgesTo:(UIView *)view {
+- (void)flex_pinEdgesTo:(UIView *)view {
    [NSLayoutConstraint activateConstraints:@[
        [self.topAnchor constraintEqualToAnchor:view.topAnchor],
        [self.leftAnchor constraintEqualToAnchor:view.leftAnchor],
@@ -42,7 +42,7 @@
    ]]; 
 }
 
-- (void)pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)i {
+- (void)flex_pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)i {
     [NSLayoutConstraint activateConstraints:@[
         [self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
         [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
@@ -51,15 +51,15 @@
     ]];
 }
 
-- (void)pinEdgesToSuperview {
-    [self pinEdgesTo:self.superview];
+- (void)flex_pinEdgesToSuperview {
+    [self flex_pinEdgesTo:self.superview];
 }
 
-- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets {
-    [self pinEdgesTo:self.superview withInsets:insets];
+- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets {
+    [self flex_pinEdgesTo:self.superview withInsets:insets];
 }
 
-- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i aboveView:(UIView *)sibling {
+- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i aboveView:(UIView *)sibling {
     UIView *view = self.superview;
     [NSLayoutConstraint activateConstraints:@[
         [self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
@@ -69,7 +69,7 @@
     ]];
 }
 
-- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i belowView:(UIView *)sibling {
+- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i belowView:(UIView *)sibling {
     UIView *view = self.superview;
     [NSLayoutConstraint activateConstraints:@[
         [self.topAnchor constraintEqualToAnchor:sibling.bottomAnchor constant:i.top],

+ 2 - 2
Classes/Utility/Categories/UIGestureRecognizer+Blocks.h

@@ -13,9 +13,9 @@ typedef void (^GestureBlock)(UIGestureRecognizer *gesture);
 
 @interface UIGestureRecognizer (Blocks)
 
-+ (instancetype)action:(GestureBlock)action;
++ (instancetype)flex_action:(GestureBlock)action;
 
-@property (nonatomic) GestureBlock action;
+@property (nonatomic, setter=flex_setAction:) GestureBlock flex_action;
 
 @end
 

+ 5 - 5
Classes/Utility/Categories/UIGestureRecognizer+Blocks.m

@@ -14,22 +14,22 @@
 
 static void * actionKey;
 
-+ (instancetype)action:(GestureBlock)action {
++ (instancetype)flex_action:(GestureBlock)action {
     UIGestureRecognizer *gesture = [[self alloc] initWithTarget:nil action:nil];
     [gesture addTarget:gesture action:@selector(flex_invoke)];
-    gesture.action = action;
+    gesture.flex_action = action;
     return gesture;
 }
 
 - (void)flex_invoke {
-    self.action(self);
+    self.flex_action(self);
 }
 
-- (GestureBlock)action {
+- (GestureBlock)flex_action {
     return objc_getAssociatedObject(self, &actionKey);
 }
 
-- (void)setAction:(GestureBlock)action {
+- (void)flex_setAction:(GestureBlock)action {
     objc_setAssociatedObject(self, &actionKey, action, OBJC_ASSOCIATION_COPY);
 }
 

+ 1 - 1
Classes/Utility/Categories/UITextField+Range.h

@@ -9,6 +9,6 @@
 
 @interface UITextField (Range)
 
-@property (nonatomic, readonly) NSRange selectedRange;
+@property (nonatomic, readonly) NSRange flex_selectedRange;
 
 @end

+ 1 - 1
Classes/Utility/Categories/UITextField+Range.m

@@ -9,7 +9,7 @@
 
 @implementation UITextField (Range)
 
-- (NSRange)selectedRange {
+- (NSRange)flex_selectedRange {
     UITextRange *r = self.selectedTextRange;
     if (r) {
         NSInteger loc = [self offsetFromPosition:self.beginningOfDocument toPosition:r.start];

+ 0 - 1
Classes/Utility/FLEXUtility.h

@@ -15,7 +15,6 @@
 #import "FLEXAlert.h"
 #import "NSArray+FLEX.h"
 #import "UIFont+FLEX.h"
-#import "NSMapTable+FLEX_Subscripting.h"
 #import "FLEXMacros.h"
 
 #if !FLEX_AT_LEAST_IOS13_SDK

+ 1 - 0
Classes/Utility/Runtime/FLEXRuntimeUtility.h

@@ -53,6 +53,7 @@
 
 /// Used to describe an object in brief within an explorer row
 + (NSString *)summaryForObject:(id)value;
++ (NSString *)safeClassNameForObject:(id)object;
 + (NSString *)safeDescriptionForObject:(id)object;
 + (NSString *)safeDebugDescriptionForObject:(id)object;
 

+ 21 - 15
Classes/Utility/Runtime/FLEXRuntimeUtility.m

@@ -96,10 +96,18 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
     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.
-    // Check to make sure the object responds to the description method
+    // Don't assume that we have an NSObject subclass; not all objects respond to -description
     if ([self safeObject:object respondsToSelector:@selector(description)]) {
         return [object description];
     }
@@ -111,8 +119,6 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
 + (NSString *)safeDebugDescriptionForObject:(id)object {
     NSString *description = nil;
 
-    // Don't assume that we have an NSObject subclass.
-    // Check to make sure the object responds to the description method
     if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) {
         description = [object debugDescription];
     } else {
@@ -177,18 +183,18 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
 }
 
 + (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel {
-    static BOOL (*respondsToSelector)(id, SEL, SEL) = nil;
-    static BOOL (*respondsToSelector_meta)(id, SEL, SEL) = nil;
-    static dispatch_once_t onceToken;
-    dispatch_once(&onceToken, ^{
-        respondsToSelector = (BOOL(*)(id, SEL, SEL))[NSObject instanceMethodForSelector:@selector(respondsToSelector:)];
-        respondsToSelector_meta = (BOOL(*)(id, SEL, SEL))[NSObject methodForSelector:@selector(respondsToSelector:)];
-    });
-    
+    // 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);
-    return (isClass ? respondsToSelector_meta : respondsToSelector)(
-        object, @selector(respondsToSelector:), sel
-    );
+    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;
+    }
 }
 
 

+ 1 - 0
Classes/ViewHierarchy/TreeExplorer/FLEXHierarchyTableViewController.m

@@ -8,6 +8,7 @@
 
 #import "FLEXColor.h"
 #import "FLEXHierarchyTableViewController.h"
+#import "NSMapTable+FLEX_Subscripting.h"
 #import "FLEXUtility.h"
 #import "FLEXHierarchyTableViewCell.h"
 #import "FLEXObjectExplorerViewController.h"

+ 38 - 15
FLEX.xcodeproj/project.pbxproj

@@ -567,6 +567,7 @@
 		C36B096623E0D4A1008F5D47 /* UIMenu+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */; };
 		C36B097023E1EDCD008F5D47 /* FLEXTableViewSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C36B096E23E1EDCD008F5D47 /* FLEXTableViewSection.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		C36B097123E1EDCD008F5D47 /* FLEXTableViewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C36B096F23E1EDCD008F5D47 /* FLEXTableViewSection.m */; };
+		C36E1B26259D64CC00FEFEF6 /* FLEXNewRootClass.m in Sources */ = {isa = PBXBuildFile; fileRef = C36E1B25259D64CC00FEFEF6 /* FLEXNewRootClass.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
 		C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */; };
 		C36FBFCC230F3B98008D95D5 /* FLEXProtocolBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */; };
@@ -946,6 +947,8 @@
 		C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIMenu+FLEX.m"; sourceTree = "<group>"; };
 		C36B096E23E1EDCD008F5D47 /* FLEXTableViewSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTableViewSection.h; sourceTree = "<group>"; };
 		C36B096F23E1EDCD008F5D47 /* FLEXTableViewSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewSection.m; sourceTree = "<group>"; };
+		C36E1B24259D64CC00FEFEF6 /* FLEXNewRootClass.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXNewRootClass.h; sourceTree = "<group>"; };
+		C36E1B25259D64CC00FEFEF6 /* FLEXNewRootClass.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXNewRootClass.m; sourceTree = "<group>"; };
 		C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMirror.m; sourceTree = "<group>"; };
 		C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXProtocolBuilder.h; sourceTree = "<group>"; };
 		C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMethod.m; sourceTree = "<group>"; };
@@ -1497,6 +1500,23 @@
 			path = Objc;
 			sourceTree = "<group>";
 		};
+		C31E4E53259D4A4100712288 /* Private */ = {
+			isa = PBXGroup;
+			children = (
+				C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */,
+				C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */,
+				C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */,
+				C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */,
+				C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */,
+				C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */,
+				C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */,
+				C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */,
+				C398627423AD79B6007E6793 /* NSString+FLEX.h */,
+				C398627523AD79B7007E6793 /* NSString+FLEX.m */,
+			);
+			path = Private;
+			sourceTree = "<group>";
+		};
 		C33CF16B22D664E600F9C6C0 /* FileBrowser */ = {
 			isa = PBXGroup;
 			children = (
@@ -1573,6 +1593,15 @@
 			path = Cells;
 			sourceTree = "<group>";
 		};
+		C36E1B27259D64D300FEFEF6 /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				C36E1B24259D64CC00FEFEF6 /* FLEXNewRootClass.h */,
+				C36E1B25259D64CC00FEFEF6 /* FLEXNewRootClass.m */,
+			);
+			path = "Supporting Files";
+			sourceTree = "<group>";
+		};
 		C36FBFB8230F3B52008D95D5 /* Runtime */ = {
 			isa = PBXGroup;
 			children = (
@@ -1626,10 +1655,9 @@
 		C387C88022E0D22600750E58 /* Categories */ = {
 			isa = PBXGroup;
 			children = (
+				C31E4E53259D4A4100712288 /* Private */,
 				C3DFCDB62418336D00BB7084 /* NSUserDefaults+FLEX.h */,
 				C3DFCDB72418336D00BB7084 /* NSUserDefaults+FLEX.m */,
-				C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */,
-				C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */,
 				C398627023AD7951007E6793 /* UIGestureRecognizer+Blocks.h */,
 				C398627123AD7951007E6793 /* UIGestureRecognizer+Blocks.m */,
 				C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */,
@@ -1644,18 +1672,10 @@
 				C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */,
 				C3F977812311B38F0032776D /* NSObject+FLEX_Reflection.h */,
 				C3F977822311B38F0032776D /* NSObject+FLEX_Reflection.m */,
-				C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */,
-				C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */,
-				C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */,
-				C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */,
-				C398627423AD79B6007E6793 /* NSString+FLEX.h */,
-				C398627523AD79B7007E6793 /* NSString+FLEX.m */,
 				C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */,
 				C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */,
 				C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */,
 				C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */,
-				C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */,
-				C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */,
 				C36B096323E0D4A1008F5D47 /* UIMenu+FLEX.h */,
 				C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */,
 				C3694DC023EA147F006625D7 /* UIBarButtonItem+FLEX.h */,
@@ -2277,7 +2297,7 @@
 					3A4C941E1B5B20570088C3F2 = {
 						CreatedOnToolsVersion = 6.4;
 						LastSwiftMigration = 1130;
-						ProvisioningStyle = Manual;
+						ProvisioningStyle = Automatic;
 					};
 				};
 			};
@@ -2353,6 +2373,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				C36E1B26259D64CC00FEFEF6 /* FLEXNewRootClass.m in Sources */,
 				C33C825B23159EAF00DD2451 /* FLEXTests.m in Sources */,
 				1C27A8B91F0E5A0400F0D02D /* FLEXTestsMethodsList.m in Sources */,
 				C3854DF023F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m in Sources */,
@@ -2956,9 +2977,10 @@
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
 				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
 				CLANG_WARN_STRICT_PROTOTYPES = NO;
-				CODE_SIGN_IDENTITY = "";
+				CODE_SIGN_IDENTITY = "Apple Development";
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
-				CODE_SIGN_STYLE = Manual;
+				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+				CODE_SIGN_STYLE = Automatic;
 				DEFINES_MODULE = YES;
 				DEVELOPMENT_TEAM = "";
 				DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2996,9 +3018,10 @@
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
 				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
 				CLANG_WARN_STRICT_PROTOTYPES = NO;
-				CODE_SIGN_IDENTITY = "";
+				CODE_SIGN_IDENTITY = "Apple Development";
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
-				CODE_SIGN_STYLE = Manual;
+				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+				CODE_SIGN_STYLE = Automatic;
 				DEFINES_MODULE = YES;
 				DEVELOPMENT_TEAM = "";
 				DYLIB_COMPATIBILITY_VERSION = 1;

+ 20 - 1
FLEXTests/FLEXTests.m

@@ -13,8 +13,10 @@
 #import "FLEXPropertyAttributes.h"
 #import "FLEXProperty.h"
 #import "FLEXUtility.h"
+#import "FLEXRuntimeUtility.h"
 #import "FLEXMethod.h"
 #import "FLEXIvar.h"
+#import "FLEXNewRootClass.h"
 
 @interface Subclass : NSObject {
     @public
@@ -39,6 +41,7 @@
 - (void)testAssumptionsAboutClasses {
     Class cls = [self class];
     Class meta = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
+    Class rootMeta = object_getClass(meta);
 
     // Subsequent `class` calls yield self
     XCTAssertEqual(cls, [cls class]);
@@ -50,7 +53,7 @@
 
     // Subsequent object_getClass calls yield metaclass
     XCTAssertEqual(object_getClass(cls), meta);
-    XCTAssertEqual(object_getClass(meta), meta);
+    XCTAssertEqual(object_getClass(object_getClass(meta)), rootMeta);
 
     // Superclass of a root class is nil
     XCTAssertNil(NSObject.superclass);
@@ -121,4 +124,20 @@
     XCTAssertEqual(pointerValue[0], 0xaa);
 }
 
+- (void)testSafeRespondsToSelector {
+    XCTAssertFalse([FLEXRuntimeUtility
+        safeObject:[NSObject class] respondsToSelector:@selector(testSafeRespondsToSelector)
+    ]);
+    
+    Class root = NSClassFromString(@"FLEXNewRootClass");
+    XCTAssertTrue([FLEXRuntimeUtility safeObject:root respondsToSelector:@selector(theOnlyMethod)]);
+    XCTAssertFalse([FLEXRuntimeUtility safeObject:root respondsToSelector:@selector(class)]);
+}
+
+- (void)testSafeGetClassName {
+    id instance = [NSClassFromString(@"FLEXNewRootClass") alloc];
+    NSString *className = [FLEXRuntimeUtility safeClassNameForObject:instance];
+    XCTAssertEqualObjects(@"FLEXNewRootClass", className);
+}
+
 @end

+ 19 - 0
FLEXTests/Supporting Files/FLEXNewRootClass.h

@@ -0,0 +1,19 @@
+//
+//  FLEXNewRootClass.h
+//  FLEXTests
+//
+//  Created by Tanner on 12/30/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+/// Root class with one method
+OBJC_ROOT_CLASS
+@interface FLEXNewRootClass {
+    Class isa OBJC_ISA_AVAILABILITY;
+}
+
+- (void)theOnlyMethod;
+
+@end

+ 25 - 0
FLEXTests/Supporting Files/FLEXNewRootClass.m

@@ -0,0 +1,25 @@
+//
+//  FLEXNewRootClass.m
+//  FLEXTests
+//
+//  Created by Tanner on 12/30/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXNewRootClass.h"
+#import <objc/runtime.h>
+
+@implementation FLEXNewRootClass
+
++ (id)alloc {
+    FLEXNewRootClass *obj = (__bridge id)calloc(1, class_getInstanceSize(self));
+    object_setClass(obj, self);
+    return obj;
+}
+
+- (void)theOnlyMethod { }
+
+- (void)retain { }
+- (void)release { }
+
+@end