Explorar o código

Replace classes/libraries with "runtime browser"

Tanner Bennett %!s(int64=6) %!d(string=hai) anos
pai
achega
6d4c7b5e0d
Modificáronse 51 ficheiros con 1042 adicións e 885 borrados
  1. 12 2
      Classes/Core/FLEXTableViewController.h
  2. 18 4
      Classes/Core/FLEXTableViewController.m
  3. 1 1
      Classes/GlobalStateExplorers/FLEXAddressExplorerCoordinator.m
  4. 0 16
      Classes/GlobalStateExplorers/FLEXClassesTableViewController.h
  5. 0 152
      Classes/GlobalStateExplorers/FLEXClassesTableViewController.m
  6. 0 14
      Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.h
  7. 0 199
      Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.m
  8. 1 2
      Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h
  9. 4 8
      Classes/GlobalStateExplorers/Globals/FLEXGlobalsTableViewController.m
  10. 13 8
      Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntime.h
  11. 48 32
      Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntime.m
  12. 15 7
      Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntimeController.h
  13. 42 32
      Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntimeController.m
  14. 14 0
      Classes/GlobalStateExplorers/RuntimeBrowser/FLEXObjcRuntimeViewController.h
  15. 105 0
      Classes/GlobalStateExplorers/RuntimeBrowser/FLEXObjcRuntimeViewController.m
  16. 4 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPath.h
  17. 16 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPath.m
  18. 9 10
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathSearchController.h
  19. 183 134
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathSearchController.m
  20. 2 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathTokenizer.h
  21. 2 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathTokenizer.m
  22. 2 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathToolbar.h
  23. 2 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathToolbar.m
  24. 0 14
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathViewController.h
  25. 0 123
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathViewController.m
  26. 2 3
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyboardToolbar.h
  27. 75 22
      Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyboardToolbar.m
  28. 3 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBToken.h
  29. 12 2
      Classes/GlobalStateExplorers/RuntimeBrowser/TBToken.m
  30. 3 3
      Classes/GlobalStateExplorers/RuntimeBrowser/TBToolbarButton.h
  31. 88 23
      Classes/GlobalStateExplorers/RuntimeBrowser/TBToolbarButton.m
  32. 6 6
      Classes/ObjectExplorers/FLEXObjectExplorer.m
  33. 8 10
      Classes/ObjectExplorers/FLEXObjectExplorerViewController.m
  34. 17 0
      Classes/ObjectExplorers/Views/FLEXCodeFontCell.h
  35. 23 0
      Classes/ObjectExplorers/Views/FLEXCodeFontCell.m
  36. 5 8
      Classes/ObjectExplorers/Views/FLEXMultilineTableViewCell.m
  37. 3 0
      Classes/ObjectExplorers/Views/FLEXTableView.h
  38. 8 1
      Classes/ObjectExplorers/Views/FLEXTableView.m
  39. 5 0
      Classes/ObjectExplorers/Views/FLEXTableViewCell.h
  40. 11 7
      Classes/ObjectExplorers/Views/FLEXTableViewCell.m
  41. 29 0
      Classes/Utility/Categories/NSObject+Reflection.m
  42. 1 1
      Classes/Utility/Categories/NSString+KeyPaths.h
  43. 1 1
      Classes/Utility/Categories/NSString+KeyPaths.m
  44. 18 0
      Classes/Utility/Categories/NSTimer+Blocks.h
  45. 25 0
      Classes/Utility/Categories/NSTimer+Blocks.m
  46. 14 0
      Classes/Utility/Categories/UITextField+Range.h
  47. 23 0
      Classes/Utility/Categories/UITextField+Range.m
  48. 9 1
      Classes/Utility/Runtime/FLEXMethod.m
  49. 27 0
      Classes/Utility/Runtime/FLEXRuntimeSafety.h
  50. 92 0
      Classes/Utility/Runtime/FLEXRuntimeSafety.m
  51. 41 25
      FLEX.xcodeproj/project.pbxproj

+ 12 - 2
Classes/Core/FLEXTableViewController.h

@@ -19,7 +19,14 @@ extern CGFloat const kFLEXDebounceForAsyncSearch;
 /// The least frequent, at just over once per second; for I/O or other expensive operations
 extern CGFloat const kFLEXDebounceForExpensiveIO;
 
-@interface FLEXTableViewController : UITableViewController <UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate>
+@protocol FLEXSearchResultsUpdating <NSObject>
+- (void)updateSearchResults:(NSString *)newText;
+@end
+
+@interface FLEXTableViewController : UITableViewController <
+    UISearchResultsUpdating, UISearchControllerDelegate,
+    UISearchBarDelegate, FLEXSearchResultsUpdating
+>
 
 /// A grouped table view. Inset on iOS 13.
 /// 
@@ -48,7 +55,6 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
 /// nil unless showsSearchBar is set to YES.
 /// 
 /// self is used as the default search results updater and delegate.
-/// Make sure your subclass conforms to UISearchControllerDelegate.
 /// The search bar will not dim the background or hide the navigation bar by default.
 /// On iOS 11 and up, the search bar will appear in the navigation bar below the title.
 @property (nonatomic) UISearchController *searchController;
@@ -82,6 +88,10 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
 /// self.searchController.searchBar.text
 @property (nonatomic, readonly) NSString *searchText;
 
+/// A totally optional delegate to forward search results updater calls to.
+/// If a delegate is set, updateSearchResults: is not called on this view controller. 
+@property (nonatomic, weak    ) id<FLEXSearchResultsUpdating> searchResultsUpdater;
+
 /// Subclasses should override to handle search query update events.
 /// 
 /// searchBarDebounceInterval is used to reduce the frequency at which this method is called.

+ 18 - 4
Classes/Core/FLEXTableViewController.m

@@ -24,6 +24,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
 @interface FLEXTableViewController ()
 @property (nonatomic) NSTimer *debounceTimer;
 @property (nonatomic) BOOL didInitiallyRevealSearchBar;
+@property (nonatomic) UITableViewStyle style;
 @end
 
 @implementation FLEXTableViewController
@@ -50,6 +51,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
     if (self) {
         _searchBarDebounceInterval = kFLEXDebounceFast;
         _showSearchBarInitially = YES;
+        _style = style;
     }
     
     return self;
@@ -157,6 +159,12 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
 
 #pragma mark - View Controller Lifecycle
 
+- (void)loadView {
+    self.view = [FLEXTableView style:self.style];
+    self.tableView.dataSource = self;
+    self.tableView.delegate = self;
+}
+
 - (void)viewDidLoad {
     [super viewDidLoad];
     
@@ -237,14 +245,20 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
     [self.debounceTimer invalidate];
     NSString *text = searchController.searchBar.text;
     
+    void (^updateSearchResults)() = ^{
+        if (self.searchResultsUpdater) {
+            [self.searchResultsUpdater updateSearchResults:text];
+        } else {
+            [self updateSearchResults:text];
+        }
+    };
+    
     // Only debounce if we want to, and if we have a non-empty string
     // Empty string events are sent instantly
     if (text.length && self.searchBarDebounceInterval > kFLEXDebounceInstant) {
-        [self debounce:^{
-            [self updateSearchResults:text];
-        }];
+        [self debounce:updateSearchResults];
     } else {
-        [self updateSearchResults:text];
+        updateSearchResults();
     }
 }
 

+ 1 - 1
Classes/GlobalStateExplorers/FLEXAddressExplorerCoordinator.m

@@ -23,7 +23,7 @@
 #pragma mark - FLEXGlobalsEntry
 
 + (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
-    return @"🔎 Address Explorer";
+    return @"🔎  Address Explorer";
 }
 
 + (FLEXGlobalsTableViewControllerRowAction)globalsEntryRowAction:(FLEXGlobalsRow)row {

+ 0 - 16
Classes/GlobalStateExplorers/FLEXClassesTableViewController.h

@@ -1,16 +0,0 @@
-//
-//  FLEXClassesTableViewController.h
-//  Flipboard
-//
-//  Created by Ryan Olson on 2014-05-03.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXTableViewController.h"
-#import "FLEXGlobalsEntry.h"
-
-@interface FLEXClassesTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
-
-+ (instancetype)binaryImageName:(NSString *)binaryImageName;
-
-@end

+ 0 - 152
Classes/GlobalStateExplorers/FLEXClassesTableViewController.m

@@ -1,152 +0,0 @@
-//
-//  FLEXClassesTableViewController.m
-//  Flipboard
-//
-//  Created by Ryan Olson on 2014-05-03.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXClassesTableViewController.h"
-#import "FLEXObjectExplorerViewController.h"
-#import "FLEXObjectExplorerFactory.h"
-#import "FLEXUtility.h"
-#import <objc/runtime.h>
-
-@interface FLEXClassesTableViewController ()
-
-@property (nonatomic, copy) NSArray<NSString *> *classNames;
-@property (nonatomic, copy) NSArray<NSString *> *filteredClassNames;
-@property (nonatomic, copy) NSString *binaryImageName;
-
-@end
-
-@implementation FLEXClassesTableViewController
-
-#pragma mark - Initialization
-
-+ (instancetype)binaryImageName:(NSString *)binaryImageName
-{
-    return [[self alloc] initWithBinaryImageName:binaryImageName];
-}
-
-- (id)initWithBinaryImageName:(NSString *)binaryImageName
-{
-    NSParameterAssert(binaryImageName);
-
-    self = [super init];
-    if (self) {
-        self.binaryImageName = binaryImageName;
-        [self loadClassNames];
-    }
-
-    return self;
-}
-
-
-#pragma mark - Internal
-
-- (void)viewDidLoad
-{
-    [super viewDidLoad];
-    
-    self.showsSearchBar = YES;
-    [self updateTitle];
-}
-
-- (void)updateTitle
-{
-    NSString *shortImageName = self.binaryImageName.lastPathComponent;
-    self.title = [NSString stringWithFormat:@"%@ Classes (%lu)",
-        shortImageName, (unsigned long)self.filteredClassNames.count
-    ];
-}
-
-- (void)setClassNames:(NSArray<NSString *> *)classNames
-{
-    _classNames = self.filteredClassNames = classNames.copy;
-}
-
-- (void)loadClassNames
-{
-    unsigned int classNamesCount = 0;
-    const char **classNames = objc_copyClassNamesForImage(self.binaryImageName.UTF8String, &classNamesCount);
-    if (classNames) {
-        NSMutableArray<NSString *> *classNameStrings = [NSMutableArray array];
-        for (unsigned int i = 0; i < classNamesCount; i++) {
-            const char *className = classNames[i];
-            NSString *classNameString = [NSString stringWithUTF8String:className];
-            [classNameStrings addObject:classNameString];
-        }
-        
-        self.classNames = [classNameStrings sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
-        
-        free(classNames);
-    }
-}
-
-
-#pragma mark - FLEXGlobalsEntry
-
-+ (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
-    return [NSString stringWithFormat:@"📕  %@ Classes", [FLEXUtility applicationName]];
-}
-
-+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
-    return [self binaryImageName:[FLEXUtility applicationImageName]];
-}
-
-
-#pragma mark - Search bar
-
-- (void)updateSearchResults:(NSString *)searchText
-{
-    if (searchText.length > 0) {
-        NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", searchText];
-        self.filteredClassNames = [self.classNames filteredArrayUsingPredicate:searchPredicate].reverseObjectEnumerator.allObjects;
-    } else {
-        self.filteredClassNames = self.classNames;
-    }
-    [self updateTitle];
-    [self.tableView reloadData];
-}
-
-
-#pragma mark - Table View Data Source
-
-- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
-{
-    return 1;
-}
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-{
-    return self.filteredClassNames.count;
-}
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    static NSString *CellIdentifier = @"Cell";
-    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
-    if (!cell) {
-        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
-        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
-        cell.textLabel.font = UIFont.flex_defaultTableCellFont;
-    }
-    
-    cell.textLabel.text = self.filteredClassNames[indexPath.row];
-    
-    return cell;
-}
-
-
-#pragma mark - Table View Delegate
-
-- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    NSString *className = self.filteredClassNames[indexPath.row];
-    Class selectedClass = objc_getClass(className.UTF8String);
-    FLEXObjectExplorerViewController *objectExplorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:selectedClass];
-    [self.navigationController pushViewController:objectExplorer animated:YES];
-}
-
-@end

+ 0 - 14
Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.h

@@ -1,14 +0,0 @@
-//
-//  FLEXLibrariesTableViewController.h
-//  Flipboard
-//
-//  Created by Ryan Olson on 2014-05-02.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXTableViewController.h"
-#import "FLEXGlobalsEntry.h"
-
-@interface FLEXLibrariesTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
-
-@end

+ 0 - 199
Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.m

@@ -1,199 +0,0 @@
-//
-//  FLEXLibrariesTableViewController.m
-//  Flipboard
-//
-//  Created by Ryan Olson on 2014-05-02.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXLibrariesTableViewController.h"
-#import "FLEXUtility.h"
-#import "FLEXClassesTableViewController.h"
-#import "FLEXObjectExplorerFactory.h"
-#import <objc/runtime.h>
-
-@interface FLEXLibrariesTableViewController ()
-
-@property (nonatomic) NSArray<NSString *> *imageNames;
-@property (nonatomic) NSArray<NSString *> *filteredImageNames;
-@property (nonatomic) NSString *headerTitle;
-
-@property (nonatomic) Class foundClass;
-
-@end
-
-@implementation FLEXLibrariesTableViewController
-
-- (id)initWithStyle:(UITableViewStyle)style
-{
-    self = [super initWithStyle:style];
-    if (self) {
-        [self loadImageNames];
-    }
-    return self;
-}
-
-- (void)viewDidLoad
-{
-    [super viewDidLoad];
-    
-    self.showsSearchBar = YES;
-    [self updateHeaderTitle];
-}
-
-- (void)updateHeaderTitle
-{
-    if (self.foundClass) {
-        self.headerTitle = @"Looking for this?";
-    } else if (self.imageNames.count == self.filteredImageNames.count) {
-        // Unfiltered
-        self.headerTitle = [NSString stringWithFormat:@"%@ libraries", @(self.imageNames.count)];
-    } else {
-        self.headerTitle = [NSString
-            stringWithFormat:@"%@ of %@ libraries",
-            @(self.filteredImageNames.count), @(self.imageNames.count)
-        ];
-    }
-}
-
-
-#pragma mark - FLEXGlobalsEntry
-
-+ (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
-    return @"📚  System Libraries";
-}
-
-+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
-    FLEXLibrariesTableViewController *librariesViewController = [self new];
-    librariesViewController.title = [self globalsEntryTitle:row];
-
-    return librariesViewController;
-}
-
-
-#pragma mark - Binary Images
-
-- (void)loadImageNames
-{
-    unsigned int imageNamesCount = 0;
-    const char **imageNames = objc_copyImageNames(&imageNamesCount);
-    if (imageNames) {
-        NSMutableArray<NSString *> *imageNameStrings = [NSMutableArray array];
-        NSString *appImageName = [FLEXUtility applicationImageName];
-        for (unsigned int i = 0; i < imageNamesCount; i++) {
-            const char *imageName = imageNames[i];
-            NSString *imageNameString = [NSString stringWithUTF8String:imageName];
-            // Skip the app's image. We're just showing system libraries and frameworks.
-            if (![imageNameString isEqual:appImageName]) {
-                [imageNameStrings addObject:imageNameString];
-            }
-        }
-        
-        // Sort alphabetically
-        self.imageNames = [imageNameStrings sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(NSString *name1, NSString *name2) {
-            NSString *shortName1 = [self shortNameForImageName:name1];
-            NSString *shortName2 = [self shortNameForImageName:name2];
-            return [shortName1 caseInsensitiveCompare:shortName2];
-        }];
-        
-        free(imageNames);
-    }
-}
-
-- (NSString *)shortNameForImageName:(NSString *)imageName
-{
-    NSArray<NSString *> *components = [imageName componentsSeparatedByString:@"/"];
-    if (components.count >= 2) {
-        return [NSString stringWithFormat:@"%@/%@", components[components.count - 2], components[components.count - 1]];
-    }
-    return imageName.lastPathComponent;
-}
-
-- (void)setImageNames:(NSArray<NSString *> *)imageNames
-{
-    if (![_imageNames isEqual:imageNames]) {
-        _imageNames = imageNames;
-        self.filteredImageNames = imageNames;
-    }
-}
-
-
-#pragma mark - Filtering
-
-- (void)updateSearchResults:(NSString *)searchText
-{
-    if (searchText.length) {
-        NSPredicate *searchPredicate = [NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary<NSString *, id> *bindings) {
-            BOOL matches = NO;
-            NSString *shortName = [self shortNameForImageName:evaluatedObject];
-            if ([shortName rangeOfString:searchText options:NSCaseInsensitiveSearch].length > 0) {
-                matches = YES;
-            }
-            return matches;
-        }];
-        self.filteredImageNames = [self.imageNames filteredArrayUsingPredicate:searchPredicate];
-    } else {
-        self.filteredImageNames = self.imageNames;
-    }
-    
-    self.foundClass = NSClassFromString(searchText);
-    [self updateHeaderTitle];
-    [self.tableView reloadData];
-}
-
-
-#pragma mark - Table View Data Source
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-{
-    return self.filteredImageNames.count + (self.foundClass ? 1 : 0);
-}
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    static NSString *cellIdentifier = @"Cell";
-    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
-    if (!cell) {
-        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
-        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
-        cell.textLabel.font = UIFont.flex_defaultTableCellFont;
-    }
-    
-    NSString *executablePath;
-    if (self.foundClass) {
-        if (indexPath.row == 0) {
-            cell.textLabel.text = [NSString stringWithFormat:@"Class \"%@\"", self.searchText];
-            return cell;
-        } else {
-            executablePath = self.filteredImageNames[indexPath.row-1];
-        }
-    } else {
-        executablePath = self.filteredImageNames[indexPath.row];
-    }
-    
-    cell.textLabel.text = [self shortNameForImageName:executablePath];
-    return cell;
-}
-
-- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
-{
-    return self.headerTitle;
-}
-
-
-#pragma mark - Table View Delegate
-
-- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    if (indexPath.row == 0 && self.foundClass) {
-        [self.navigationController pushViewController:[FLEXObjectExplorerFactory
-            explorerViewControllerForObject:self.foundClass
-        ] animated:YES];
-    } else {
-        [self.navigationController pushViewController:[FLEXClassesTableViewController
-            binaryImageName:self.filteredImageNames[self.foundClass ? 0 : indexPath.row]
-        ] animated:YES];
-    }
-}
-
-@end

+ 1 - 2
Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h

@@ -17,8 +17,7 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
     FLEXGlobalsRowLiveObjects,
     FLEXGlobalsRowAddressInspector,
     FLEXGlobalsRowCookies,
-    FLEXGlobalsRowSystemLibraries,
-    FLEXGlobalsRowAppClasses,
+    FLEXGlobalsRowBrowseRuntime,
     FLEXGlobalsRowAppKeychainItems,
     FLEXGlobalsRowAppDelegate,
     FLEXGlobalsRowRootViewController,

+ 4 - 8
Classes/GlobalStateExplorers/Globals/FLEXGlobalsTableViewController.m

@@ -9,8 +9,7 @@
 #import "FLEXGlobalsTableViewController.h"
 #import "FLEXUtility.h"
 #import "FLEXRuntimeUtility.h"
-#import "FLEXLibrariesTableViewController.h"
-#import "FLEXClassesTableViewController.h"
+#import "FLEXObjcRuntimeViewController.h"
 #import "FLEXKeychainTableViewController.h"
 #import "FLEXObjectExplorerViewController.h"
 #import "FLEXObjectExplorerFactory.h"
@@ -55,14 +54,12 @@ static __weak UIWindow *s_applicationWindow = nil;
 + (FLEXGlobalsEntry *)globalsEntryForRow:(FLEXGlobalsRow)row
 {
     switch (row) {
-        case FLEXGlobalsRowAppClasses:
-            return [FLEXClassesTableViewController flex_concreteGlobalsEntry:row];
         case FLEXGlobalsRowAppKeychainItems:
             return [FLEXKeychainTableViewController flex_concreteGlobalsEntry:row];
         case FLEXGlobalsRowAddressInspector:
             return [FLEXAddressExplorerCoordinator flex_concreteGlobalsEntry:row];
-        case FLEXGlobalsRowSystemLibraries:
-            return [FLEXLibrariesTableViewController flex_concreteGlobalsEntry:row];
+        case FLEXGlobalsRowBrowseRuntime:
+            return [FLEXObjcRuntimeViewController flex_concreteGlobalsEntry:row];
         case FLEXGlobalsRowLiveObjects:
             return [FLEXLiveObjectsTableViewController flex_concreteGlobalsEntry:row];
         case FLEXGlobalsRowCookies:
@@ -113,8 +110,7 @@ static __weak UIWindow *s_applicationWindow = nil;
                 [self globalsEntryForRow:FLEXGlobalsRowProcessInfo],
                 [self globalsEntryForRow:FLEXGlobalsRowLiveObjects],
                 [self globalsEntryForRow:FLEXGlobalsRowAddressInspector],
-                [self globalsEntryForRow:FLEXGlobalsRowSystemLibraries],
-                [self globalsEntryForRow:FLEXGlobalsRowAppClasses],
+                [self globalsEntryForRow:FLEXGlobalsRowBrowseRuntime],
             ],
             @[ // FLEXGlobalsSectionAppShortcuts
                 [self globalsEntryForRow:FLEXGlobalsRowBrowseBundle],

+ 13 - 8
Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntime.h

@@ -1,6 +1,6 @@
 //
-//  TBRuntime.h
-//  TBTweakViewController
+//  FLEXRuntime.h
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -23,17 +23,22 @@
 /// An array of strings representing the currently loaded libraries.
 @property (nonatomic, readonly) NSArray<NSString*> *imageDisplayNames;
 
+/// "Image name" is the path of the bundle
 - (NSString *)shortNameForImageName:(NSString *)imageName;
+/// "Image name" is the path of the bundle
+- (NSString *)imageNameForShortName:(NSString *)imageName;
 
 /// @return Bundle names for the UI
-- (NSMutableArray<NSString*> *)bundleNamesForToken:(TBToken *)token;
+- (NSMutableArray<NSString *> *)bundleNamesForToken:(TBToken *)token;
 /// @return Bundle paths for more queries
-- (NSMutableArray<NSString*> *)bundlePathsForToken:(TBToken *)token;
+- (NSMutableArray<NSString *> *)bundlePathsForToken:(TBToken *)token;
 /// @return Class names
-- (NSMutableArray<NSString*> *)classesForToken:(TBToken *)token inBundles:(NSMutableArray<NSString*> *)bundlePaths;
-/// @return Actual methods
-- (NSMutableArray<FLEXMethod*> *)methodsForToken:(TBToken *)token
+- (NSMutableArray<NSString *> *)classesForToken:(TBToken *)token
+                                      inBundles:(NSMutableArray<NSString *> *)bundlePaths;
+/// @return A list of lists of \c FLEXMethods where
+/// each list corresponds to one of the given classes
+- (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForToken:(TBToken *)token
                                       instance:(NSNumber *)onlyInstanceMethods
-                                     inClasses:(NSMutableArray<NSString*> *)classes;
+                                     inClasses:(NSArray<NSString*> *)classes;
 
 @end

+ 48 - 32
Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntime.m

@@ -1,6 +1,6 @@
 //
-//  TBRuntime.m
-//  TBTweakViewController
+//  FLEXRuntime.m
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -10,13 +10,13 @@
 #import "NSObject+Reflection.h"
 #import "FLEXMethod.h"
 #import "NSArray+Functional.h"
-//#import "MKRuntimeSafety.h"
+#import "FLEXRuntimeSafety.h"
 
 
-#define TBEquals(a, b) ([a compare:b options:NSCaseInsensitiveSearch] == NSOrderedSame)
-#define TBContains(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location != NSNotFound)
-#define TBHasPrefix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == 0)
-#define TBHasSuffix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == (a.length - b.length))
+#define Equals(a, b)    ([a compare:b options:NSCaseInsensitiveSearch] == NSOrderedSame)
+#define Contains(a, b)  ([a rangeOfString:b options:NSCaseInsensitiveSearch].location != NSNotFound)
+#define HasPrefix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == 0)
+#define HasSuffix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == (a.length - b.length))
 
 
 @interface TBRuntime () {
@@ -24,6 +24,7 @@
 }
 
 @property (nonatomic) NSMutableDictionary *bundles_pathToShort;
+@property (nonatomic) NSMutableDictionary *bundles_shortToPath;
 @property (nonatomic) NSCache *bundles_pathToClassNames;
 @property (nonatomic) NSMutableArray<NSString*> *imagePaths;
 
@@ -34,20 +35,20 @@ static inline NSString * TBWildcardMap_(NSString *token, NSString *candidate, NS
     switch (options) {
         case TBWildcardOptionsNone:
             // Only "if equals"
-            if (TBEquals(candidate, token)) {
+            if (Equals(candidate, token)) {
                 return success;
             }
         default: {
             // Only "if contains"
             if (options & TBWildcardOptionsPrefix &&
                 options & TBWildcardOptionsSuffix) {
-                if (TBContains(candidate, token)) {
+                if (Contains(candidate, token)) {
                     return success;
                 }
             }
             // Only "if candidate ends with with token"
             else if (options & TBWildcardOptionsPrefix) {
-                if (TBHasSuffix(candidate, token)) {
+                if (HasSuffix(candidate, token)) {
                     return success;
                 }
             }
@@ -57,7 +58,7 @@ static inline NSString * TBWildcardMap_(NSString *token, NSString *candidate, NS
                 if (!token.length) {
                     return success;
                 }
-                if (TBHasPrefix(candidate, token)) {
+                if (HasPrefix(candidate, token)) {
                     return success;
                 }
             }
@@ -90,8 +91,9 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
 - (id)init {
     self = [super init];
     if (self) {
-        _imagePaths = [NSMutableArray array];
-        _bundles_pathToShort = [NSMutableDictionary dictionary];
+        _imagePaths = [NSMutableArray new];
+        _bundles_pathToShort = [NSMutableDictionary new];
+        _bundles_shortToPath = [NSMutableDictionary new];
         _bundles_pathToClassNames = [NSCache new];
     }
 
@@ -137,7 +139,11 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
     if (components.count >= 2) {
         NSString *parentDir = components[components.count - 2];
         if ([parentDir hasSuffix:@".framework"] || [parentDir hasSuffix:@".axbundle"]) {
-            shortName = parentDir;
+            if ([imageName hasSuffix:@".dylib"]) {
+                shortName = imageName.lastPathComponent;
+            } else {
+                shortName = parentDir;
+            }
         }
     }
 
@@ -146,9 +152,14 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
     }
 
     _bundles_pathToShort[imageName] = shortName;
+    _bundles_shortToPath[shortName] = imageName;
     return shortName;
 }
 
+- (NSString *)imageNameForShortName:(NSString *)imageName {
+    return _bundles_shortToPath[imageName];
+}
+
 - (NSMutableArray<NSString*> *)classNamesInImageAtPath:(NSString *)path {
     // Check cache
     NSMutableArray *classNameStrings = [_bundles_pathToClassNames objectForKey:path];
@@ -189,8 +200,8 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
 
         // No dot syntax because imageDisplayNames is only mutable internally
         return [_imageDisplayNames flex_mapped:^id(NSString *binary, NSUInteger idx) {
-            NSString *UIName = [self shortNameForImageName:binary];
-            return TBWildcardMap(query, UIName, options);
+//            NSString *UIName = [self shortNameForImageName:binary];
+            return TBWildcardMap(query, binary, options);
         }];
     }
 
@@ -218,9 +229,9 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
 }
 
 - (NSMutableArray<NSString*> *)classesForToken:(TBToken *)token inBundles:(NSMutableArray<NSString*> *)bundles {
-    // Edge case where token is the class we want already
+    // Edge case where token is the class we want already; return superclasses
     if (token.isAbsolute) {
-        if (MKClassIsSafe(NSClassFromString(token.string))) {
+        if (FLEXClassIsSafe(NSClassFromString(token.string))) {
             return [NSMutableArray arrayWithObject:token.string];
         }
 
@@ -231,7 +242,7 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
         // Get class names, remove unsafe classes
         NSMutableArray<NSString*> *names = [self _classesForToken:token inBundles:bundles];
         return [names flex_mapped:^NSString *(NSString *cls, NSUInteger idx) {
-            NSSet *ignored = MKKnownUnsafeClassNames();
+            NSSet *ignored = FLEXKnownUnsafeClassNames();
             BOOL safe = ![ignored containsObject:cls];
             return safe ? cls : nil;
         }];
@@ -258,12 +269,12 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
     else {
         // Optimization, avoid a loop
         if (options == TBWildcardOptionsAny) {
-            return [[bundles flex_mapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
+            return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
                 return [self classNamesInImageAtPath:bundlePath];
             }] sortedUsingSelector:@selector(caseInsensitiveCompare:)];
         }
 
-        return [[bundles flex_mapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
+        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);
             }];
@@ -271,24 +282,29 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
     }
 }
 
-- (NSMutableArray<FLEXMethod*> *)methodsForToken:(TBToken *)token
-                                      instance:(NSNumber *)checkInstance
-                                     inClasses:(NSMutableArray *)classes {
+- (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForToken:(TBToken *)token
+                                                    instance:(NSNumber *)checkInstance
+                                                   inClasses:(NSArray<NSString *> *)classes {
     if (classes.count) {
         TBWildcardOptions options = token.options;
         BOOL instance = checkInstance.boolValue;
         NSString *selector = token.string;
 
         switch (options) {
-            /// In practice, I don't think this case is ever used with methods
+            // In practice I don't think this case is ever used with methods,
+            // since they will always have a suffix wildcard at the end
             case TBWildcardOptionsNone: {
                 SEL sel = (SEL)selector.UTF8String;
-                return [classes flex_mapped:^id(NSString *name, NSUInteger idx) {
+                return @[[classes flex_mapped:^id(NSString *name, NSUInteger idx) {
                     Class cls = NSClassFromString(name);
-
+                    // Use metaclass if not instance
+                    if (!instance) {
+                        cls = object_getClass(cls);
+                    }
+                    
                     // Method is absolute
-                    return [MKLazyMethod methodForSelector:sel class:cls instance:instance];
-                }];
+                    return [FLEXMethod selector:sel class:cls];
+                }]];
             }
             case TBWildcardOptionsAny: {
                 return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
@@ -306,7 +322,7 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
                         return [[cls flex_allMethods] flex_mapped:^id(FLEXMethod *method, NSUInteger idx) {
 
                             // Method is a prefix-suffix wildcard
-                            if (TBContains(method.selectorString, selector)) {
+                            if (Contains(method.selectorString, selector)) {
                                 return method;
                             }
                             return nil;
@@ -320,7 +336,7 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
 
                         return [[cls flex_allMethods] flex_mapped:^id(FLEXMethod *method, NSUInteger idx) {
                             // Method is a prefix wildcard
-                            if (TBHasSuffix(method.selectorString, selector)) {
+                            if (HasSuffix(method.selectorString, selector)) {
                                 return method;
                             }
                             return nil;
@@ -345,7 +361,7 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
 
                         id mapping = ^id(FLEXMethod *method) {
                             // Method is a suffix wildcard
-                            if (TBHasPrefix(method.selectorString, selector)) {
+                            if (HasPrefix(method.selectorString, selector)) {
                                 return method;
                             }
                             return nil;

+ 15 - 7
Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntimeController.h

@@ -1,9 +1,8 @@
 //
-//  TBRuntimeController.h
-//  TBTweakViewController
+//  FLEXRuntimeController.h
+//  FLEX
 //
 //  Created by Tanner on 3/23/17.
-//  Copyright © 2017 Tanner Bennett. All rights reserved.
 //
 
 #import "TBKeyPath.h"
@@ -12,15 +11,24 @@
 @interface TBRuntimeController : NSObject
 
 /// @return An array of strings if the key path only evaluates
-///         to a class or bundle; otherwise, an array of FLEXMethods.
+///         to a class or bundle; otherwise, a list of lists of FLEXMethods.
 + (NSArray *)dataForKeyPath:(TBKeyPath *)keyPath;
 
-+ (NSDictionary *)methodsForToken:(TBToken *)token
-                         instance:(NSNumber *)onlyInstanceMethods
-                        inClasses:(NSArray<NSString*> *)classes;
+/// Useful when you need to specify which classes to search in.
+/// \c dataForKeyPath: will only search classes matching the class key.
+/// We use this elsewhere when we need to search a class hierarchy.
++ (NSArray<NSArray<FLEXMethod *> *> *)methodsForToken:(TBToken *)token
+                                             instance:(NSNumber *)onlyInstanceMethods
+                                            inClasses:(NSArray<NSString*> *)classes;
+
+/// Useful when you need the classes that are associated with the
+/// double list of methods returned from \c dataForKeyPath
++ (NSMutableArray<NSString *> *)classesForKeyPath:(TBKeyPath *)keyPath;
 
 + (NSString *)shortBundleNameForClass:(NSString *)name;
 
++ (NSString *)imagePathWithShortName:(NSString *)suffix;
+
 + (NSArray<NSString*> *)allBundleNames;
 
 @end

+ 42 - 32
Classes/GlobalStateExplorers/RuntimeBrowser/DataSources/TBRuntimeController.m

@@ -1,9 +1,8 @@
 //
-//  TBRuntimeController.m
-//  TBTweakViewController
+//  FLEXRuntimeController.m
+//  FLEX
 //
 //  Created by Tanner on 3/23/17.
-//  Copyright © 2017 Tanner Bennett. All rights reserved.
 //
 
 #import "TBRuntimeController.h"
@@ -52,7 +51,7 @@ static TBRuntimeController *controller = nil;
             if (keyPath.methodKey) {
                 return [[self shared] methodsForKeyPath:keyPath];
             } else {
-                return [[self shared] classesForClassToken:keyPath.classKey andBundleToken:keyPath.bundleKey];
+                return [[self shared] classesForKeyPath:keyPath];
             }
         } else {
             return [[self shared] bundleNamesForToken:keyPath.bundleKey];
@@ -62,18 +61,18 @@ static TBRuntimeController *controller = nil;
     }
 }
 
-+ (NSDictionary *)methodsForToken:(TBToken *)token
++ (NSArray<NSArray<FLEXMethod *> *> *)methodsForToken:(TBToken *)token
                          instance:(NSNumber *)inst
                         inClasses:(NSArray<NSString*> *)classes {
-    NSMutableDictionary *methods = [NSMutableDictionary dictionary];
-    for (NSString *className in classes) {
-        NSMutableArray *target = [NSMutableArray arrayWithObject:className];
-        methods[className] = [[TBRuntime runtime] methodsForToken:token
-                                                         instance:inst
-                                                        inClasses:target];
-    }
+    return [[TBRuntime runtime]
+        methodsForToken:token
+        instance:inst
+        inClasses:classes
+    ];
+}
 
-    return methods;
++ (NSMutableArray<NSString *> *)classesForKeyPath:(TBKeyPath *)keyPath {
+    return [[self shared] classesForKeyPath:keyPath];
 }
 
 + (NSString *)shortBundleNameForClass:(NSString *)name {
@@ -81,6 +80,10 @@ static TBRuntimeController *controller = nil;
     return [[TBRuntime runtime] shortNameForImageName:imagePath];
 }
 
++ (NSString *)imagePathWithShortName:(NSString *)suffix {
+    return [[TBRuntime runtime] imageNameForShortName:suffix];
+}
+
 + (NSArray *)allBundleNames {
     return [TBRuntime runtime].imageDisplayNames;
 }
@@ -106,7 +109,7 @@ static TBRuntimeController *controller = nil;
     }
 }
 
-- (NSMutableArray<NSString*> *)bundleNamesForToken:(TBToken *)token {
+- (NSMutableArray<NSString *> *)bundleNamesForToken:(TBToken *)token {
     // Only cache if no wildcard
     BOOL shouldCache = token == TBWildcardOptionsNone;
 
@@ -125,21 +128,24 @@ static TBRuntimeController *controller = nil;
     }
 }
 
-- (NSMutableArray<NSString*> *)classesForClassToken:(TBToken *)clsToken andBundleToken:(TBToken *)bundleToken {
+- (NSMutableArray<NSString *> *)classesForKeyPath:(TBKeyPath *)keyPath {
+    TBToken *classToken = keyPath.classKey;
+    TBToken *bundleToken = keyPath.bundleKey;
+    
     // Only cache if no wildcard
-    BOOL shouldCache = bundleToken.options == 0 && clsToken.options == 0;
+    BOOL shouldCache = bundleToken.options == 0 && classToken.options == 0;
     NSString *key = nil;
 
     if (shouldCache) {
-        key = [@[bundleToken.description, clsToken.description] componentsJoinedByString:@"+"];
+        key = [@[bundleToken.description, classToken.description] componentsJoinedByString:@"+"];
         NSMutableArray<NSString*> *cached = [self.classNamesCache objectForKey:key];
         if (cached) {
             return cached;
         }
     }
 
-    NSMutableArray<NSString*> *bundles = [self bundlePathsForToken:bundleToken];
-    NSMutableArray<NSString*> *classes = [[TBRuntime runtime] classesForToken:clsToken inBundles:bundles];
+    NSMutableArray *bundles = [self bundlePathsForToken:bundleToken];
+    NSMutableArray *classes = [[TBRuntime runtime] classesForToken:classToken inBundles:bundles];
 
     if (shouldCache) {
         [self.classNamesCache setObject:classes forKey:key];
@@ -148,29 +154,33 @@ static TBRuntimeController *controller = nil;
     return classes;
 }
 
-- (NSMutableArray<FLEXMethod*> *)methodsForKeyPath:(TBKeyPath *)keyPath {
+- (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForKeyPath:(TBKeyPath *)keyPath {
     // Only cache if no wildcard, but check cache anyway bc I'm lazy
-    NSMutableArray<FLEXMethod*> *cached = [self.methodsCache objectForKey:keyPath];
+    NSArray<NSMutableArray *> *cached = [self.methodsCache objectForKey:keyPath];
     if (cached) {
         return cached;
     }
 
-    NSMutableArray<NSString*> *classes = [self classesForClassToken:keyPath.classKey andBundleToken:keyPath.bundleKey];
-    NSMutableArray<FLEXMethod*> *methods = [[TBRuntime runtime] methodsForToken:keyPath.methodKey
-                                                                     instance:keyPath.instanceMethods
-                                                                    inClasses:classes];
-
-    [methods sortUsingComparator:^NSComparisonResult(FLEXMethod *m1, FLEXMethod *m2) {
-        return [m1.description caseInsensitiveCompare:m2.description];
-    }];
+    NSArray<NSString *> *classes = [self classesForKeyPath:keyPath];
+    NSArray<NSMutableArray<FLEXMethod *> *> *methodLists = [[TBRuntime runtime]
+        methodsForToken:keyPath.methodKey
+        instance:keyPath.instanceMethods
+        inClasses:classes
+    ];
+
+    for (NSMutableArray<FLEXMethod *> *methods in methodLists) {
+        [methods sortUsingComparator:^NSComparisonResult(FLEXMethod *m1, FLEXMethod *m2) {
+            return [m1.description caseInsensitiveCompare:m2.description];
+        }];
+    }
 
-    // Only cache if no wildcard
+    // Only cache if no wildcard, otherwise the cache could grow very large
     if (keyPath.bundleKey.isAbsolute &&
         keyPath.classKey.isAbsolute) {
-        [self.methodsCache setObject:methods forKey:keyPath];
+        [self.methodsCache setObject:methodLists forKey:keyPath];
     }
 
-    return methods;
+    return methodLists;
 }
 
 @end

+ 14 - 0
Classes/GlobalStateExplorers/RuntimeBrowser/FLEXObjcRuntimeViewController.h

@@ -0,0 +1,14 @@
+//
+//  FLEXObjcRuntimeViewController.h
+//  FLEX
+//
+//  Created by Tanner on 3/23/17.
+//  Copyright © 2017 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXTableViewController.h"
+#import "FLEXGlobalsEntry.h"
+
+@interface FLEXObjcRuntimeViewController : FLEXTableViewController <FLEXGlobalsEntry>
+
+@end

+ 105 - 0
Classes/GlobalStateExplorers/RuntimeBrowser/FLEXObjcRuntimeViewController.m

@@ -0,0 +1,105 @@
+//
+//  FLEXObjcRuntimeViewController.m
+//  FLEX
+//
+//  Created by Tanner on 3/23/17.
+//  Copyright © 2017 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXObjcRuntimeViewController.h"
+#import "TBKeyPathSearchController.h"
+#import "TBKeyPathToolbar.h"
+#import "UIGestureRecognizer+Blocks.h"
+#import "FLEXTableView.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXAlert.h"
+
+
+@interface FLEXObjcRuntimeViewController () <TBKeyPathSearchControllerDelegate>
+
+@property (nonatomic, readonly ) TBKeyPathSearchController *keyPathController;
+@property (nonatomic, readonly ) UIView *promptView;
+
+// .@property (nonatomic, readonly) void (^callback)();
+
+@end
+
+@implementation FLEXObjcRuntimeViewController
+
+#pragma mark - Setup, view events
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+
+    self.title = @"📚  Runtime Browser";
+    
+    // Search bar stuff, must be first because this creates self.searchController
+    self.showsSearchBar = YES;
+    self.pinSearchBar = YES;
+    self.searchController.searchBar.placeholder = @"UIKit*.UIView.-setFrame:";
+
+    // Search controller stuff
+    // key path controller automatically assigns itself as the delegate of the search bar
+    // To avoid a retain cycle below, use local variables
+    UISearchBar *searchBar = self.searchController.searchBar;
+    TBKeyPathSearchController *keyPathController = [TBKeyPathSearchController delegate:self];
+    _keyPathController = keyPathController;
+    _keyPathController.toolbar = [TBKeyPathToolbar toolbarWithHandler:^(NSString *buttonTitle) {
+        [keyPathController didPressButton:buttonTitle insertInto:searchBar];
+    }];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    
+    dispatch_async(dispatch_get_main_queue(), ^{
+        // This doesn't work unless it's wrapped in this dispatch_async call
+        [self.searchController.searchBar becomeFirstResponder];
+    });
+}
+
+
+#pragma mark Delegate stuff
+
+- (void)didSelectImagePath:(NSString *)path shortName:(NSString *)shortName {
+    [FLEXAlert makeAlert:^(FLEXAlert *make) {
+        make.title(shortName);
+        make.message(@"No NSBundle associated with this path:\n\n");
+        make.message(path);
+
+        make.button(@"Copy Path").handler(^(NSArray<NSString *> *strings) {
+            UIPasteboard.generalPasteboard.string = path;
+        });
+        make.button(@"Dismiss");
+    } showFrom:self];
+}
+
+- (void)didSelectBundle:(NSBundle *)bundle {
+    NSParameterAssert(bundle);
+    FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:bundle];
+    [self.navigationController pushViewController:explorer animated:YES];
+}
+
+- (void)didSelectClass:(Class)cls {
+    NSParameterAssert(cls);
+    FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:cls];
+    [self.navigationController pushViewController:explorer animated:YES];
+}
+
+
+#pragma mark - FLEXGlobalsEntry
+
++ (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
+    return @"📚  Runtime Browser";
+}
+
++ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
+    return [self new];
+}
+
+@end

+ 4 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPath.h

@@ -1,6 +1,6 @@
 //
-//  TBKeyPath.h
-//  TBTweakViewController
+//  FLEXKeyPath.h
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
 /// a key path from a string.
 @interface TBKeyPath : NSObject
 
++ (instancetype)empty;
+
 /// @param method must start with either a wildcard or a + or -.
 + (instancetype)bundle:(TBToken *)bundle
                  class:(TBToken *)cls

+ 16 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPath.m

@@ -1,6 +1,6 @@
 //
-//  TBKeyPath.m
-//  TBTweakViewController
+//  FLEXKeyPath.m
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -16,6 +16,20 @@
 
 @implementation TBKeyPath
 
++ (instancetype)empty {
+    static TBKeyPath *empty = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        TBToken *any = [TBToken any];
+
+        empty = [self new];
+        empty->_bundleKey = any;
+        empty->tb_description = @"";
+    });
+
+    return empty;
+}
+
 + (instancetype)bundle:(TBToken *)bundle
                  class:(TBToken *)cls
                 method:(TBToken *)method

+ 9 - 10
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathSearchController.h

@@ -1,6 +1,6 @@
 //
-//  TBKeyPathSearchController.h
-//  TBTweakViewController
+//  FLEXKeyPathSearchController.h
+//  FLEX
 //
 //  Created by Tanner on 3/23/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -10,27 +10,26 @@
 #import "TBKeyPathToolbar.h"
 #import "FLEXMethod.h"
 
+@protocol TBKeyPathSearchControllerDelegate <UITableViewDataSource>
 
-@protocol TBKeyPathSearchControllerDelegate <NSObject>
 @property (nonatomic, readonly) UITableView *tableView;
-@property (nonatomic, readonly) UISearchBar *searchBar;
-@property (nonatomic, readonly) UINavigationController *navigationController;
+@property (nonatomic, readonly) UISearchController *searchController;
 
-@property (nonatomic, readonly) NSString *longPressItemSELPrefix;
-
-- (void)didSelectMethod:(FLEXMethod *)method;
+/// For loaded images which don't have an NSBundle
+- (void)didSelectImagePath:(NSString *)message shortName:(NSString *)shortName;
+- (void)didSelectBundle:(NSBundle *)bundle;
+- (void)didSelectClass:(Class)cls;
 
 @end
 
+
 @interface TBKeyPathSearchController : NSObject <UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate>
 
 + (instancetype)delegate:(id<TBKeyPathSearchControllerDelegate>)delegate;
 
 @property (nonatomic) TBKeyPathToolbar *toolbar;
 
-- (void)longPressedRect:(CGRect)rect at:(NSIndexPath *)indexPath;
 - (void)didSelectKeyPathOption:(NSString *)text;
-- (void)didSelectSuperclass:(NSString *)name;
 - (void)didPressButton:(NSString *)text insertInto:(UISearchBar *)searchBar;
 
 @end

+ 183 - 134
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathSearchController.m

@@ -1,6 +1,6 @@
 //
-//  TBKeyPathSearchController.m
-//  TBTweakViewController
+//  FLEXKeyPathSearchController.m
+//  FLEX
 //
 //  Created by Tanner on 3/23/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -9,14 +9,13 @@
 #import "TBKeyPathSearchController.h"
 #import "TBKeyPathTokenizer.h"
 #import "TBRuntimeController.h"
-//#import "TBCodeFontCell.h"
 #import "NSString+KeyPaths.h"
-#import "Categories.h"
-
-#import "TBConfigureHookViewController.h"
-#import "TBTweakManager.h"
-#import "TBMethodHook.h"
-
+#import "NSArray+Functional.h"
+#import "UITextField+Range.h"
+#import "NSTimer+Blocks.h"
+#import "FLEXTableView.h"
+#import "FLEXUtility.h"
+#import "FLEXObjectExplorerFactory.h"
 
 @interface TBKeyPathSearchController ()
 @property (nonatomic, readonly, weak) id<TBKeyPathSearchControllerDelegate> delegate;
@@ -24,13 +23,14 @@
 @property (nonatomic) NSArray<NSString*> *bundlesOrClasses;
 @property (nonatomic) TBKeyPath *keyPath;
 
-// We use this when the target class is not absolute
-@property (nonatomic) NSArray<FLEXMethod*> *methods;
-
-// We use these when the target class is absolute and has superclasses.
-// Contrary to the name, superclasses contains the origin class name as well.
-@property (nonatomic) NSArray<NSString*> *superclasses;
-@property (nonatomic) NSDictionary<NSString*, NSArray*> *classesToMethods;
+/// Used to track which methods go with which classes. This is used in
+/// two scenarios: (1) when the target class is absolute and has classes,
+/// (this list will include the "leaf" class as well as parent classes in this case)
+/// or (2) when the class key is a wildcard and we're searching methods in many
+/// classes at once. Each list in \c classesToMethods correspnds to a class here.
+@property (nonatomic) NSArray<NSString *> *classes;
+// We use this regardless of whether the target class is absolute, just as above
+@property (nonatomic) NSArray<NSArray<FLEXMethod *> *> *classesToMethods;
 @end
 
 #warning TODO there's no code to handle refreshing the table after manually appending ".bar" to "Bundle"
@@ -42,118 +42,73 @@
     controller->_delegate         = delegate;
 
     NSParameterAssert(delegate.tableView);
-    NSParameterAssert(delegate.searchBar);
+    NSParameterAssert(delegate.searchController);
 
     delegate.tableView.delegate   = controller;
     delegate.tableView.dataSource = controller;
-    delegate.searchBar.delegate   = controller;
+    delegate.searchController.searchBar.delegate = controller;    
 
     return controller;
 }
 
 - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
     if (scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating) {
-        [self.delegate.searchBar resignFirstResponder];
+        [self.delegate.searchController.searchBar resignFirstResponder];
     }
 }
 
-#pragma mark Long press on class cell
-
-- (void)longPressedRect:(CGRect)rect at:(NSIndexPath *)indexPath {
-    UIMenuController *menuController = [UIMenuController sharedMenuController];
-    menuController.menuItems = [self menuItemsForRow:indexPath.row];
-    if (menuController.menuItems) {
-        [self.delegate.searchBar resignFirstResponder];
-        [menuController setTargetRect:rect inView:self.delegate.tableView];
-        [menuController setMenuVisible:YES animated:YES];
-    }
+- (void)setToolbar:(TBKeyPathToolbar *)toolbar {
+    _toolbar = toolbar;
+    self.delegate.searchController.searchBar.inputAccessoryView = toolbar;
 }
 
-- (NSArray *)menuItemsForRow:(NSUInteger)row {
-    if (!self.keyPath.methodKey && self.keyPath.classKey) {
-        NSArray<NSString*> *superclasses = [self superclassesOf:self.bundlesOrClasses[row]];
-
-        // Map to UIMenuItems, will delegate call into didSelectKeyPathOption:
-        return [superclasses flex_mapped:^id(NSString *cls, NSUInteger idx) {
-            NSString *sel = [self.delegate.longPressItemSELPrefix stringByAppendingString:cls];
-            return [[UIMenuItem alloc] initWithTitle:cls action:NSSelectorFromString(sel)];
-        }];
-    }
-
-    return nil;
-}
-
-- (NSArray<NSString*> *)superclassesOf:(NSString *)className {
+- (NSArray<NSString *> *)classesOf:(NSString *)className {
     Class baseClass = NSClassFromString(className);
+    if (!baseClass) {
+        return @[];
+    }
 
-    // Find superclasses
-    NSMutableArray<NSString*> *superclasses = [NSMutableArray array];
+    // Find classes
+    NSMutableArray<NSString*> *classes = [NSMutableArray arrayWithObject:className];
     while ([baseClass superclass]) {
-        [superclasses addObject:NSStringFromClass([baseClass superclass])];
+        [classes addObject:NSStringFromClass([baseClass superclass])];
         baseClass = [baseClass superclass];
     }
 
-    return superclasses;
+    return classes;
 }
 
 #pragma mark Key path stuff
 
-- (void)didSelectSuperclass:(NSString *)name {
-    NSString *bundle = [TBRuntimeController shortBundleNameForClass:name];
-    bundle = [bundle stringByReplacingOccurrencesOfString:@"." withString:@"\\."];
-    NSString *newText = [NSString stringWithFormat:@"%@.%@.", bundle, name];
-    self.delegate.searchBar.text = newText;
-
-    // Update list
-    self.keyPath = [TBKeyPathTokenizer tokenizeString:newText];
-    [self didSelectAbsoluteClass:name];
-    [self updateTable];
-}
-
 - (void)didSelectKeyPathOption:(NSString *)text {
     [_timer invalidate]; // Still might be waiting to refresh when method is selected
 
     // Change "Bundle.fooba" to "Bundle.foobar."
-    NSString *orig = self.delegate.searchBar.text;
+    NSString *orig = self.delegate.searchController.searchBar.text;
     NSString *keyPath = [orig stringByReplacingLastKeyPathComponent:text];
-    self.delegate.searchBar.text = keyPath;
+    self.delegate.searchController.searchBar.text = keyPath;
 
     self.keyPath = [TBKeyPathTokenizer tokenizeString:keyPath];
 
-    // Get superclasses if class was selected
+    // Get classes if class was selected
     if (self.keyPath.classKey.isAbsolute && self.keyPath.methodKey.isAny) {
         [self didSelectAbsoluteClass:text];
     } else {
-        self.superclasses = nil;
+        self.classes = nil;
     }
 
     [self updateTable];
 }
 
-- (void)didSelectMethod:(FLEXMethod *)method {
-    // If the user selects a method implemented only by a superclass,
-    // we're going to be adding a method. We need to take the given
-    // method and change it's target class to the base class.
-    Class target = NSClassFromString(self.keyPath.classKey.string);
-    if (self.keyPath.classKey.isAbsolute && method.targetClass != target) {
-        #warning TODO clean this up
-        method = [FLEXMethod method:method.objc_method class:target isInstanceMethod:method.isInstanceMethod];
-    }
-
-    [self.delegate didSelectMethod:method];
-}
-
 - (void)didSelectAbsoluteClass:(NSString *)name {
-    NSMutableArray *superclasses = [NSMutableArray array];
-    [superclasses addObject:name];
-    [superclasses addObjectsFromArray:[self superclassesOf:name]];
-    self.superclasses     = superclasses;
+    self.classes          = [self classesOf:name];
     self.bundlesOrClasses = nil;
-    self.methods          = nil;
+    self.classesToMethods = nil;
 }
 
 - (void)didPressButton:(NSString *)text insertInto:(UISearchBar *)searchBar {
-    UITextField *field = [searchBar valueForKey:@"_searchField"];
+    // 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]) {
         [field replaceRange:field.selectedTextRange withText:text];
@@ -163,44 +118,72 @@
 #pragma mark - Filtering + UISearchBarDelegate
 
 - (void)updateTable {
+    // Compute the method, class, or bundle lists on a background thread
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
-        if (self.superclasses) {
-            // Compute methods list, reload table
-            self.classesToMethods = [TBRuntimeController methodsForToken:_keyPath.methodKey
-                                                                instance:_keyPath.instanceMethods
-                                                               inClasses:_superclasses];
-            dispatch_async(dispatch_get_main_queue(), ^{
-                [self.delegate.tableView reloadData];
-            });
+        if (self.classes) {
+            // Here, our class key is 'absolute'; .classes is a list of superclasses
+            // and we want to show the methods for those classes specifically
+            // TODO: add caching to this somehow
+            self.classesToMethods = [TBRuntimeController
+                methodsForToken:self.keyPath.methodKey
+                instance:self.keyPath.instanceMethods
+                inClasses:self.classes
+            ];
         }
         else {
-            NSArray *models = [TBRuntimeController dataForKeyPath:_keyPath];
-
-            dispatch_async(dispatch_get_main_queue(), ^{
-                if (_keyPath.methodKey) {
-                    _bundlesOrClasses = nil;
-                    _methods = models;
-                } else {
-                    _bundlesOrClasses = models;
-                    _methods = nil;
-                }
-
-                [self.delegate.tableView reloadData];
-            });
+            TBKeyPath *keyPath = self.keyPath;
+            NSArray *models = [TBRuntimeController dataForKeyPath:keyPath];
+            if (keyPath.methodKey) { // We're looking at methods
+                self.bundlesOrClasses = nil;
+                
+                NSMutableArray *methods = models.mutableCopy;
+                NSMutableArray *classes = [TBRuntimeController classesForKeyPath:keyPath];
+                [self setNonEmptyMethodLists:methods withClasses:classes];
+            } else { // We're looking at bundles or classes
+                self.bundlesOrClasses = models;
+                self.classesToMethods = nil;
+            }
         }
+        
+        // Finally, reload the table on the main thread
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [self.delegate.tableView reloadData];
+        });
     });
 }
 
+/// Assign .classes and .classesToMethods after removing empty sections
+- (void)setNonEmptyMethodLists:(NSMutableArray<NSArray *> *)methods withClasses:(NSMutableArray *)classes {
+    // Remove sections with no methods
+    NSIndexSet *allEmpty = [methods indexesOfObjectsPassingTest:^BOOL(NSArray *list, NSUInteger idx, BOOL *stop) {
+        return list.count == 0;
+    }];
+    [methods removeObjectsAtIndexes:allEmpty];
+    [classes removeObjectsAtIndexes:allEmpty];
+    
+    self.classes = classes;
+    self.classesToMethods = methods;
+}
+
 - (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
     // Check if character is even legal
     if (![TBKeyPathTokenizer allowedInKeyPath:text]) {
         return NO;
     }
+    
+    BOOL terminatedToken = NO;
+    BOOL isAppending = range.length == 0 && range.location == searchBar.text.length;
+    if (isAppending && [text isEqualToString:@"."]) {
+        terminatedToken = YES;
+    }
 
     // Actually parse input
     @try {
         text = [searchBar.text stringByReplacingCharactersInRange:range withString:text] ?: text;
         self.keyPath = [TBKeyPathTokenizer tokenizeString:text];
+        if (self.keyPath.classKey.isAbsolute && terminatedToken) {
+            [self didSelectAbsoluteClass:self.keyPath.classKey.string];
+        }
     } @catch (id e) {
         return NO;
     }
@@ -217,7 +200,7 @@
     // Schedule update timer
     if (searchText.length) {
         if (!self.keyPath.methodKey) {
-            self.superclasses = nil;
+            self.classes = nil;
         }
 
         _timer = [NSTimer fireSecondsFromNow:0.15 block:^{
@@ -227,11 +210,23 @@
     // ... or remove all rows
     else {
         _bundlesOrClasses = [TBRuntimeController allBundleNames];
-        _methods = nil;
+        _classesToMethods = nil;
+        _classes = nil;
+        _keyPath = nil;
         [self.delegate.tableView reloadData];
     }
 }
 
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
+    self.keyPath = [TBKeyPath empty];
+    [self updateTable];
+}
+
+/// Restore key path when going "back" and activating search bar again
+- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
+    searchBar.text = self.keyPath.description;
+}
+
 - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
     [_timer invalidate];
     [searchBar resignFirstResponder];
@@ -241,65 +236,119 @@
 #pragma mark UITableViewDataSource
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
-    return _superclasses.count ?: 1;
+    return self.classes.count ?: 1;
 }
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (_superclasses) {
-        return _classesToMethods[_superclasses[section]].count;
-    }
-
-    NSArray *models = (id)_bundlesOrClasses ?: (id)_methods;
-    return models.count;
+    return self.classes.count ? 1 : self.bundlesOrClasses.count;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    UITableViewCell *cell = [TBCodeFontCell dequeue:tableView indexPath:indexPath];
-    if (self.bundlesOrClasses) {
-        cell.accessoryType        = UITableViewCellAccessoryNone;
+    UITableViewCell *cell = [tableView
+        dequeueReusableCellWithIdentifier:kFLEXMultilineDetailCell
+        forIndexPath:indexPath
+    ];
+    
+    if (self.bundlesOrClasses.count) {
+        cell.accessoryType        = UITableViewCellAccessoryDetailButton;
         cell.textLabel.text       = self.bundlesOrClasses[indexPath.row];
         cell.detailTextLabel.text = nil;
+        if (self.keyPath.classKey) {
+            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+        }
     }
-    else if (self.superclasses) {
-        NSString *className       = self.superclasses[indexPath.section];
-        FLEXMethod *method          = self.classesToMethods[className][indexPath.row];
+    // One row per section
+    else if (self.classes.count) {
+        NSArray<FLEXMethod *> *methods = self.classesToMethods[indexPath.section];
+        NSMutableString *summary = [NSMutableString new];
+        [methods enumerateObjectsUsingBlock:^(FLEXMethod *method, NSUInteger idx, BOOL *stop) {
+            NSString *format = nil;
+            if (idx == methods.count-1) {
+                format = @"%@%@";
+                *stop = YES;
+            } else if (idx < 3) {
+                format = @"%@%@\n";
+            } else {
+                format = @"%@%@\n…";
+                *stop = YES;
+            }
+
+            [summary appendFormat:format, method.isInstanceMethod ? @"-" : @"+", method.selectorString];
+        }];
+
         cell.accessoryType        = UITableViewCellAccessoryDisclosureIndicator;
-        cell.textLabel.text       = method.fullName;
-        cell.detailTextLabel.text = method.selectorString;
+        cell.textLabel.text       = self.classes[indexPath.section];
+        cell.detailTextLabel.text = summary.length ? summary : nil;
+
     }
     else {
-        cell.accessoryType        = UITableViewCellAccessoryDisclosureIndicator;
-        cell.textLabel.text       = self.methods[indexPath.row].fullName;
-        cell.detailTextLabel.text = self.methods[indexPath.row].selectorString;
+        @throw NSInternalInconsistencyException;
     }
 
     return cell;
 }
 
 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
-    if (self.superclasses) {
-        return [self.superclasses[section] stringByAppendingString:@" methods"];
+    if (self.classes || self.keyPath.methodKey) {
+        return @" ";
+    } else if (self.bundlesOrClasses) {
+        NSInteger count = self.bundlesOrClasses.count;
+        if (self.keyPath.classKey) {
+            return FLEXPluralString(count, @"classes", @"class");
+        } else {
+            return FLEXPluralString(count, @"bundles", @"bundle");
+        }
     }
 
-    return nil;
+    return [self.delegate tableView:tableView titleForHeaderInSection:section];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
+    if (self.classes || self.keyPath.methodKey) {
+        if (section == 0) {
+            return 55;
+        }
+
+        return 0;
+    }
+
+    return 55;
 }
 
 #pragma mark UITableViewDelegate
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     if (self.bundlesOrClasses) {
-        tableView.contentOffset = CGPointMake(0, - self.delegate.searchBar.frame.size.height - 20);
-        [self didSelectKeyPathOption:self.bundlesOrClasses[indexPath.row]];
+        NSString *bundleSuffixOrClass = self.bundlesOrClasses[indexPath.row];
+        if (self.keyPath.classKey) {
+            NSParameterAssert(NSClassFromString(bundleSuffixOrClass));
+            [self.delegate didSelectClass:NSClassFromString(bundleSuffixOrClass)];
+        } else {
+            // Selected a bundle
+            [self didSelectKeyPathOption:bundleSuffixOrClass];
+        }
     } else {
-        if (self.superclasses) {
-            NSString *superclass = self.superclasses[indexPath.section];
-            [self didSelectMethod:self.classesToMethods[superclass][indexPath.row]];
+        if (self.classes) {
+            Class cls = NSClassFromString(self.classes[indexPath.section]);
+            NSParameterAssert(cls);
+            [self.delegate didSelectClass:cls];
         } else {
-            assert(indexPath.section == 0);
-            [self didSelectMethod:self.methods[indexPath.row]];
+            @throw NSInternalInconsistencyException;
         }
     }
 }
 
+- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
+    NSString *bundleSuffixOrClass = self.bundlesOrClasses[indexPath.row];
+    NSString *imagePath = [TBRuntimeController imagePathWithShortName:bundleSuffixOrClass];
+    NSBundle *bundle = [NSBundle bundleWithPath:imagePath.stringByDeletingLastPathComponent];
+
+    if (bundle) {
+        [self.delegate didSelectBundle:bundle];
+    } else {
+        [self.delegate didSelectImagePath:imagePath shortName:bundleSuffixOrClass];
+    }
+}
+
 @end
 

+ 2 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathTokenizer.h

@@ -1,6 +1,6 @@
 //
-//  TBKeyPathTokenizer.h
-//  TBTweakViewController
+//  FLEXKeyPathTokenizer.h
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.

+ 2 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathTokenizer.m

@@ -1,6 +1,6 @@
 //
-//  TBKeyPathTokenizer.m
-//  TBTweakViewController
+//  FLEXKeyPathTokenizer.m
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.

+ 2 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathToolbar.h

@@ -1,6 +1,6 @@
 //
-//  TBKeyPathToolbar.h
-//  TBTweakViewController
+//  FLEXKeyPathToolbar.h
+//  FLEX
 //
 //  Created by Tanner on 6/11/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.

+ 2 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathToolbar.m

@@ -1,6 +1,6 @@
 //
-//  TBKeyPathToolbar.m
-//  TBTweakViewController
+//  FLEXKeyPathToolbar.m
+//  FLEX
 //
 //  Created by Tanner on 6/11/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.

+ 0 - 14
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathViewController.h

@@ -1,14 +0,0 @@
-//
-//  TBKeyPathViewController.h
-//  TBTweakViewController
-//
-//  Created by Tanner on 3/23/17.
-//  Copyright © 2017 Tanner Bennett. All rights reserved.
-//
-
-#import <UIKit/UIKit.h>
-
-
-@interface TBKeyPathViewController : UIViewController
-
-@end

+ 0 - 123
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathViewController.m

@@ -1,123 +0,0 @@
-//
-//  TBKeyPathViewController.m
-//  TBTweakViewController
-//
-//  Created by Tanner on 3/23/17.
-//  Copyright © 2017 Tanner Bennett. All rights reserved.
-//
-
-#import "TBKeyPathViewController.h"
-#import "TBKeyPathSearchController.h"
-#import "TBKeyPathToolbar.h"
-#import "UIGestureRecognizer+Blocks.h"
-//#import "TBCodeFontCell.h"
-
-
-@interface TBKeyPathViewController () <TBKeyPathSearchControllerDelegate>
-
-@property (nonatomic, readonly ) TBKeyPathSearchController *searchController;
-@property (nonatomic, readonly ) UIView *promptView;
-@property (nonatomic, readwrite) UITableView *tableView;
-@property (nonatomic, readwrite) UISearchBar *searchBar;
-
-// .@property (nonatomic, readonly) void (^callback)();
-
-@end
-
-@implementation TBKeyPathViewController
-@dynamic navigationController;
-
-#pragma mark - Setup, view events
-
-- (void)loadView {
-    self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
-    self.searchBar = [UISearchBar new];
-    self.view = self.tableView;
-    [self.searchBar sizeToFit];
-
-    self.tableView.tableHeaderView = self.searchBar;
-}
-
-- (void)viewDidLoad {
-    [super viewDidLoad];
-
-    self.title = @"Choose Hook";
-
-    // Search controller stuff
-    _searchController = [TBKeyPathSearchController delegate:self];
-    _searchController.toolbar = [TBKeyPathToolbar toolbarWithHandler:^(NSString *buttonTitle) {
-        [self.searchController didPressButton:buttonTitle insertInto:self.searchBar];
-    }];
-
-    // Search bar stuff
-    self.searchBar.delegate    = self.searchController;
-    self.searchBar.placeholder = @"UIKit*.UIView.-setFrame:";
-    self.searchBar.inputAccessoryView = self.searchController.toolbar;
-
-    // Table view stuff
-    self.tableView.rowHeight = UITableViewAutomaticDimension;
-//    [self.tableView registerCell:[TBCodeFontCell class]];
-
-    // Long press gesture for classes
-    [self.tableView addGestureRecognizer:[UILongPressGestureRecognizer action:^(UIGestureRecognizer *gesture) {
-        if (gesture.state == UIGestureRecognizerStateBegan) {
-            NSIndexPath *ip = [self.tableView indexPathForRowAtPoint:[gesture locationInView:self.tableView]];
-            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:ip];
-            [self.searchController longPressedRect:cell.frame at:ip];
-        }
-    }]];
-}
-
-- (void)viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-    [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
-    [self.searchBar becomeFirstResponder];
-}
-
-- (void)viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
-    [self.searchBar resignFirstResponder];
-}
-
-#pragma mark Delegate stuff
-
-- (void)didSelectMethod:(FLEXMethod *)method {
-
-}
-
-
-#pragma mark Long press action
-
-- (NSString *)longPressItemSELPrefix { return @"tb_"; }
-
-- (void)didSelectSuperclass:(NSString *)title {
-    [self.searchController didSelectSuperclass:title];
-}
-
-- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
-    return [@((char*)(void*)action) hasPrefix:self.longPressItemSELPrefix];
-}
-
-- (BOOL)canBecomeFirstResponder {
-    return YES;
-}
-
-- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
-    if ([super methodSignatureForSelector:sel]) {
-        return [super methodSignatureForSelector:sel];
-    }
-
-    return [super methodSignatureForSelector:@selector(didSelectSuperclass:)];
-}
-
-- (void)forwardInvocation:(NSInvocation *)invocation {
-    NSString *title = NSStringFromSelector([invocation selector]);
-    NSRange match = [title rangeOfString:self.longPressItemSELPrefix];
-    if (match.location == 0) {
-        [self didSelectSuperclass:[title substringFromIndex:3]];
-    } else {
-        [super forwardInvocation:invocation];
-    }
-}
-
-@end

+ 2 - 3
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyboardToolbar.h

@@ -1,8 +1,7 @@
 //
-//  TBKeyboardToolbar.h
+//  FLEXKeyboardToolbar.h
 //
-//  Created by Rudd Fawcett on 12/3/13.
-//  Copyright (c) 2013 Rudd Fawcett. All rights reserved.
+//  Created by Tanner on 6/11/17.
 //
 
 #import <UIKit/UIKit.h>

+ 75 - 22
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyboardToolbar.m

@@ -1,15 +1,14 @@
 //
-//  TBKeyboardToolbar.m
+//  FLEXKeyboardToolbar.m
 //
-//  Created by Rudd Fawcett on 12/3/13.
-//  Copyright (c) 2013 Rudd Fawcett. All rights reserved.
+//  Created by Tanner on 6/11/17.
 //
 
 #import "TBKeyboardToolbar.h"
+#import "FLEXUtility.h"
 
 #define kToolbarHeight 44
 
-
 @interface TBKeyboardToolbar ()
 
 /// The fake top border to replicate the toolbar.
@@ -17,6 +16,10 @@
 @property (nonatomic) UIView       *toolbarView;
 @property (nonatomic) UIScrollView *scrollView;
 @property (nonatomic) UIVisualEffectView *blurView;
+/// YES if appearance is set to `default`
+@property (nonatomic, readonly) BOOL useSystemAppearance;
+/// YES if the current trait collection is set to dark mode and \c useSystemAppearance is YES
+@property (nonatomic, readonly) BOOL usingDarkMode;
 @end
 
 @implementation TBKeyboardToolbar
@@ -30,8 +33,13 @@
     if (self) {
         _buttons = [buttons copy];
         
-        self.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
-        self.appearance = UIKeyboardAppearanceLight;
+        self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+        
+        if (@available(iOS 13, *)) {
+            self.appearance = UIKeyboardTypeDefault;
+        } else {
+            self.appearance = UIKeyboardAppearanceLight;
+        }
     }
     
     return self;
@@ -40,6 +48,7 @@
 - (void)setAppearance:(UIKeyboardAppearance)appearance {
     _appearance = appearance;
     
+    // Remove toolbar if it exits because it will be recreated below
     if (self.toolbarView) {
         [self.toolbarView removeFromSuperview];
     }
@@ -48,6 +57,8 @@
 }
 
 - (void)layoutSubviews {
+    [super layoutSubviews];
+    
     CGRect frame = _toolbarView.bounds;
     frame.size.height = 0.5f;
     
@@ -58,31 +69,49 @@
     _topBorder       = [CALayer layer];
     _topBorder.frame = CGRectMake(0.0f, 0.0f, self.bounds.size.width, 0.5f);
     
+    UIColor *borderColor = nil;
+    UIBlurEffectStyle style;
+    
     switch (_appearance) {
         case UIKeyboardAppearanceDefault:
+            #if FLEX_AT_LEAST_IOS13_SDK
+            if (@available(iOS 13, *)) {
+                borderColor = [UIColor systemBackgroundColor];
+                
+                if (self.usingDarkMode) {
+                    style = UIBlurEffectStyleSystemThickMaterial;
+                    self.backgroundColor = nil;
+                } else {
+                    style = UIBlurEffectStyleSystemUltraThinMaterialLight;
+                    self.backgroundColor = [UIColor colorWithWhite:0.700 alpha:0.750];
+                }
+                break;
+            }
+            #endif
         case UIKeyboardAppearanceLight: {
-            _toolbarView = [UIView new];
-            _toolbarView.backgroundColor = [UIColor colorWithRed:0.799 green:0.814 blue:0.847 alpha:1.000];
-            _topBorder.backgroundColor   = [UIColor clearColor].CGColor;
-            [_toolbarView.layer addSublayer:_topBorder];
-            [_toolbarView addSubview:[self fakeToolbar]];
+            style = UIBlurEffectStyleLight;
+            borderColor = [UIColor clearColor];
             break;
         }
         case UIKeyboardAppearanceDark: {
-            UIVisualEffect *darkBlur = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
-            _blurView = [[UIVisualEffectView alloc] initWithEffect:darkBlur];
-            _toolbarView = _blurView;
-            _topBorder.backgroundColor = [UIColor colorWithWhite:0.100 alpha:1.000].CGColor;
-            [_blurView.contentView.layer addSublayer:_topBorder];
-            [_blurView.contentView addSubview:[self fakeToolbar]];
+            style = UIBlurEffectStyleDark;
+            borderColor = [UIColor colorWithWhite:0.100 alpha:1.000];
             break;
         }
     }
     
-    _toolbarView.frame = CGRectMake(0, 0, self.bounds.size.width, kToolbarHeight);
-    _toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    UIVisualEffect *blur = [UIBlurEffect effectWithStyle:style];
+    self.blurView = [[UIVisualEffectView alloc] initWithEffect:blur];
+    [self.blurView.contentView.layer addSublayer:self.topBorder];
+    [self.blurView.contentView addSubview:[self fakeToolbar]];
+    
+    self.toolbarView = self.blurView;
+    self.toolbarView.frame = CGRectMake(0, 0, self.bounds.size.width, kToolbarHeight);
+    self.toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    
+    self.topBorder.backgroundColor = borderColor.CGColor;
     
-    return _toolbarView;
+    return self.toolbarView;
 }
 
 - (UIScrollView *)fakeToolbar {
@@ -124,14 +153,14 @@
     _scrollView.contentSize = contentSize;
 }
 
-- (void)setButtons:(NSArray<TBToolbarButton*> *)buttons {
+- (void)setButtons:(NSArray<TBToolbarButton *> *)buttons {
     [_buttons makeObjectsPerformSelector:@selector(removeFromSuperview)];
     _buttons = buttons.copy;
     
     [self addButtons];
 }
 
-- (void)setButtons:(NSArray<TBToolbarButton*> *)buttons animated:(BOOL)animated {
+- (void)setButtons:(NSArray<TBToolbarButton *> *)buttons animated:(BOOL)animated {
     if (!animated) {
         self.buttons = buttons;
         return;
@@ -169,4 +198,28 @@
     }];
 }
 
+- (BOOL)useSystemAppearance {
+    return self.appearance == UIKeyboardAppearanceDefault;
+}
+
+- (BOOL)usingDarkMode {
+    if (@available(iOS 12, *)) {
+        return self.useSystemAppearance && self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
+    }
+    
+    return self.appearance == UIKeyboardAppearanceDark;
+}
+
+- (void)traitCollectionDidChange:(UITraitCollection *)previous {
+    if (@available(iOS 12, *)) {
+        // Was darkmode toggled?
+        if (previous.userInterfaceStyle != self.traitCollection.userInterfaceStyle) {
+            if (self.useSystemAppearance) {
+                // Recreate the background view with the proper colors
+                self.appearance = self.appearance;
+            }
+        }
+    }
+}
+
 @end

+ 3 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBToken.h

@@ -1,6 +1,6 @@
 //
-//  TBToken.h
-//  TBTweakViewController
+//  FLEXToken.h
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -21,6 +21,7 @@ typedef NS_OPTIONS(NSUInteger, TBWildcardOptions)
 /// but not in the middle of the token (as of now).
 @interface TBToken : NSObject
 
++ (instancetype)any;
 + (instancetype)string:(NSString *)string options:(TBWildcardOptions)options;
 
 /// Will not contain the wildcard (*) symbol

+ 12 - 2
Classes/GlobalStateExplorers/RuntimeBrowser/TBToken.m

@@ -1,6 +1,6 @@
 //
-//  TBToken.m
-//  TBTweakViewController
+//  FLEXToken.m
+//  FLEX
 //
 //  Created by Tanner on 3/22/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
@@ -15,6 +15,16 @@
 @end
 @implementation TBToken
 
++ (instancetype)any {
+    static TBToken *any = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        any = [self string:@"" options:TBWildcardOptionsAny];
+    });
+
+    return any;
+}
+
 + (instancetype)string:(NSString *)string options:(TBWildcardOptions)options {
     TBToken *token  = [self new];
     token->_string  = string;

+ 3 - 3
Classes/GlobalStateExplorers/RuntimeBrowser/TBToolbarButton.h

@@ -1,8 +1,7 @@
 //
-//  TBToolbarButton.h
+//  FLEXToolbarButton.h
 //
-//  Created by Rudd Fawcett on 12/3/13.
-//  Copyright (c) 2013 Rudd Fawcett. All rights reserved.
+//  Created by Tanner on 6/11/17.
 //
 
 #import <UIKit/UIKit.h>
@@ -12,6 +11,7 @@ typedef void (^TBToolbarAction)(NSString *buttonTitle);
 
 @interface TBToolbarButton : UIButton
 
+/// Set to `default` to use the system appearance on iOS 13+
 @property (nonatomic) UIKeyboardAppearance appearance;
 
 + (instancetype)buttonWithTitle:(NSString *)title;

+ 88 - 23
Classes/GlobalStateExplorers/RuntimeBrowser/TBToolbarButton.m

@@ -1,17 +1,21 @@
 //
-//  TBToolbarButton.m
+//  FLEXToolbarButton.m
 //
-//  Created by Rudd Fawcett on 12/3/13.
-//  Copyright (c) 2013 Rudd Fawcett. All rights reserved.
+//  Created by Tanner on 6/11/17.
 //
 
 #import "TBToolbarButton.h"
 #import "UIFont+FLEX.h"
-
+#import "FLEXUtility.h"
 
 @interface TBToolbarButton ()
 @property (nonatomic      ) NSString *title;
 @property (nonatomic, copy) TBToolbarAction buttonPressBlock;
+@property (nonatomic      ) UIView *backgroundView;
+/// YES if appearance is set to `default`
+@property (nonatomic, readonly) BOOL useSystemAppearance;
+/// YES if the current trait collection is set to dark mode and \c useSystemAppearance is YES
+@property (nonatomic, readonly) BOOL usingDarkMode;
 @end
 
 @implementation TBToolbarButton
@@ -34,16 +38,26 @@
     self = [super init];
     if (self) {
         _title = title;
-        self.layer.cornerRadius = 5.0f;
-        self.layer.borderWidth  = 1.0f;
-        self.titleLabel.font    = [UIFont flex_codeFont];
+        self.layer.shadowOffset = CGSizeMake(0, 1);
+        self.layer.shadowOpacity = 0.25f;
+        self.layer.shadowRadius  = 0.f;
+        self.layer.cornerRadius  = 5.f;
+        self.layer.borderWidth   = 1.f;
+        self.clipsToBounds       = NO;
+        self.titleLabel.font     = [UIFont flex_codeFont];
         [self setTitle:self.title forState:UIControlStateNormal];
         [self sizeToFit];
+        
+        if (@available(iOS 13, *)) {
+            self.appearance = UIKeyboardTypeDefault;
+        } else {
+            self.appearance = UIKeyboardAppearanceLight;
+        }
+        
         CGRect frame = self.frame;
         frame.size.width  += 40;
         frame.size.height += 10;
         self.frame = frame;
-        self.appearance = UIKeyboardAppearanceLight;
     }
     
     return self;
@@ -61,28 +75,55 @@
 - (void)setAppearance:(UIKeyboardAppearance)appearance {
     _appearance = appearance;
     
+    if (self.backgroundView.superview) {
+        [self.backgroundView removeFromSuperview];
+    }
+    
+    UIColor *titleColor = nil, *borderColor = nil;
+    UIBlurEffectStyle style;
+    
     switch (_appearance) {
+        default:
         case UIKeyboardAppearanceDefault:
+            #if FLEX_AT_LEAST_IOS13_SDK
+            if (@available(iOS 13, *)) {
+                borderColor = [UIColor clearColor];
+                titleColor = [UIColor labelColor];
+                
+                if (self.usingDarkMode) {
+                    style = UIBlurEffectStyleSystemUltraThinMaterialLight;
+                } else {
+                    style = UIBlurEffectStyleSystemMaterialLight;
+                }
+                break;
+            }
+            #endif
         case UIKeyboardAppearanceLight:
-            self.backgroundColor      = [UIColor whiteColor];
-            self.layer.borderColor    = [UIColor colorWithWhite:1.000 alpha:0.500].CGColor;
-            self.titleLabel.textColor = [UIColor blackColor];
-            [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
+            borderColor = [UIColor colorWithWhite:1.000 alpha:0.500];
+            titleColor = [UIColor blackColor];
+            style = UIBlurEffectStyleRegular;
             break;
         case UIKeyboardAppearanceDark:
-            self.backgroundColor      = [UIColor colorWithWhite:0.336 alpha:1.000];
-            self.layer.borderColor    = [UIColor clearColor].CGColor;
-            self.titleLabel.textColor = [UIColor whiteColor];
-            [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
-            break;
-            
-        default:
-            self.backgroundColor      = [UIColor colorWithWhite:0.9 alpha:1.0];
-            self.layer.borderColor    = [UIColor colorWithWhite:0.8 alpha:1.0].CGColor;
-            self.titleLabel.textColor = [UIColor colorWithWhite:0.5 alpha:1.0];
-            [self setTitleColor:[UIColor colorWithWhite:0.5 alpha:1.0] forState:UIControlStateNormal];
+            borderColor = [UIColor clearColor];
+            titleColor = [UIColor whiteColor];
+            style = UIBlurEffectStyleDark;
             break;
     }
+    
+    self.layer.borderColor = borderColor.CGColor;
+    [self setTitleColor:titleColor forState:UIControlStateNormal];
+    
+    UIVisualEffect *blur = [UIBlurEffect effectWithStyle:style];
+    self.backgroundView = [[UIVisualEffectView alloc] initWithEffect:blur];
+    self.backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    self.backgroundView.layer.cornerRadius = self.layer.cornerRadius;
+    self.backgroundView.clipsToBounds = YES;
+    // Without these, the background view blocks the button's touches
+    self.backgroundView.userInteractionEnabled = NO;
+    self.backgroundView.exclusiveTouch = NO;
+    
+    [self insertSubview:self.backgroundView atIndex:0];
+    self.backgroundView.frame = self.bounds;
 }
 
 - (BOOL)isEqual:(id)object {
@@ -97,4 +138,28 @@
     return self.title.hash;
 }
 
+- (BOOL)useSystemAppearance {
+    return self.appearance == UIKeyboardAppearanceDefault;
+}
+
+- (BOOL)usingDarkMode {
+    if (@available(iOS 12, *)) {
+        return self.useSystemAppearance && self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
+    }
+    
+    return self.appearance == UIKeyboardAppearanceDark;
+}
+
+- (void)traitCollectionDidChange:(UITraitCollection *)previous {
+    if (@available(iOS 12, *)) {
+        // Was darkmode toggled?
+        if (previous.userInterfaceStyle != self.traitCollection.userInterfaceStyle) {
+            if (self.useSystemAppearance) {
+                // Recreate the background view with the proper colors
+                self.appearance = self.appearance;
+            }
+        }
+    }
+}
+
 @end

+ 6 - 6
Classes/ObjectExplorers/FLEXObjectExplorer.m

@@ -158,7 +158,7 @@
     return [list flex_filtered:^BOOL(id obj, NSUInteger idx) {
         NSString *name = [obj name];
         if ([names containsObject:name]) {
-            return nil;
+            return NO;
         } else {
             [names addObject:name];
 
@@ -166,22 +166,22 @@
             switch (kind) {
                 case FLEXMetadataKindProperties:
                     if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
-                        return nil;
+                        return NO;
                     }
                     break;
                 case FLEXMetadataKindClassProperties:
                     if ([superclass respondsToSelector:[obj likelyGetter]]) {
-                        return nil;
+                        return NO;
                     }
                     break;
                 case FLEXMetadataKindMethods:
                     if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
-                        return nil;
+                        return NO;
                     }
                     break;
                 case FLEXMetadataKindClassMethods:
                     if ([superclass respondsToSelector:NSSelectorFromString(name)]) {
-                        return nil;
+                        return NO;
                     }
                     break;
                     
@@ -189,7 +189,7 @@
                 case FLEXMetadataKindIvars: break;
             }
 
-            return obj;
+            return YES;
         }
     }];
 }

+ 8 - 10
Classes/ObjectExplorers/FLEXObjectExplorerViewController.m

@@ -41,16 +41,6 @@
 
 #pragma mark - Initialization
 
-+ (void)initialize
-{
-    if (self == [FLEXObjectExplorerViewController class]) {
-        // Initialize custom menu items for entire app
-        UIMenuItem *copyObjectAddress = [[UIMenuItem alloc] initWithTitle:@"Copy Address" action:@selector(copyObjectAddress:)];
-        UIMenuController.sharedMenuController.menuItems = @[copyObjectAddress];
-        [UIMenuController.sharedMenuController update];
-    }
-}
-
 + (instancetype)exploringObject:(id)target
 {
     return [self exploringObject:target customSection:[FLEXShortcutsSection forObject:target]];
@@ -124,6 +114,14 @@
     self.carousel.items = [self.explorer.classHierarchy flex_mapped:^id(Class cls, NSUInteger idx) {
         return NSStringFromClass(cls);
     }];
+    
+    // Initialize custom menu items for explorer screen
+    UIMenuItem *copyObjectAddress = [[UIMenuItem alloc]
+        initWithTitle:@"Copy Address"
+        action:@selector(copyObjectAddress:)
+    ];
+    UIMenuController.sharedMenuController.menuItems = @[copyObjectAddress];
+    [UIMenuController.sharedMenuController update];
 }
 
 - (void)viewWillAppear:(BOOL)animated

+ 17 - 0
Classes/ObjectExplorers/Views/FLEXCodeFontCell.h

@@ -0,0 +1,17 @@
+//
+//  FLEXCodeFontCell.h
+//  FLEX
+//
+//  Created by Tanner on 12/27/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXSubtitleTableViewCell.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLEXCodeFontCell : FLEXSubtitleTableViewCell
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 23 - 0
Classes/ObjectExplorers/Views/FLEXCodeFontCell.m

@@ -0,0 +1,23 @@
+//
+//  FLEXCodeFontCell.m
+//  FLEX
+//
+//  Created by Tanner on 12/27/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXCodeFontCell.h"
+#import "UIFont+FLEX.h"
+
+@implementation FLEXCodeFontCell
+
+- (void)postInit {
+    [super postInit];
+    
+    self.titleLabel.font = UIFont.flex_codeFont;
+    self.subtitleLabel.font = UIFont.flex_smallCodeFont;
+    self.subtitleLabel.alpha = 0.5;
+    self.subtitleLabel.adjustsFontSizeToFitWidth = YES;
+}
+
+@end

+ 5 - 8
Classes/ObjectExplorers/Views/FLEXMultilineTableViewCell.m

@@ -17,14 +17,11 @@
 
 @implementation FLEXMultilineTableViewCell
 
-- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
-    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
-    if (self) {
-        self.titleLabel.numberOfLines = 0;
-        self.subtitleLabel.numberOfLines = 0;
-    }
-
-    return self;
+- (void)postInit {
+    [super postInit];
+    
+    self.titleLabel.numberOfLines = 0;
+    self.subtitleLabel.numberOfLines = 0;
 }
 
 + (UIEdgeInsets)labelInsets {

+ 3 - 0
Classes/ObjectExplorers/Views/FLEXTableView.h

@@ -20,6 +20,8 @@ extern FLEXTableViewCellReuseIdentifier const kFLEXDetailCell;
 extern FLEXTableViewCellReuseIdentifier const kFLEXMultilineCell;
 /// A \c FLEXMultilineTableViewCell initialized with \c UITableViewCellStyleSubtitle
 extern FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell;
+/// A \c FLEXSubtitleTableViewCell which uses monospaced fonts for both labels
+extern FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell;
 
 #pragma mark - FLEXTableView
 @interface FLEXTableView : UITableView
@@ -27,6 +29,7 @@ extern FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell;
 + (instancetype)flexDefaultTableView;
 + (instancetype)groupedTableView;
 + (instancetype)plainTableView;
++ (instancetype)style:(UITableViewStyle)style;
 
 /// You do not need to register classes for any of the default reuse identifiers above
 /// (annotated as \c FLEXTableViewCellReuseIdentifier types) unless you wish to provide

+ 8 - 1
Classes/ObjectExplorers/Views/FLEXTableView.m

@@ -10,11 +10,13 @@
 #import "FLEXUtility.h"
 #import "FLEXSubtitleTableViewCell.h"
 #import "FLEXMultilineTableViewCell.h"
+#import "FLEXCodeFontCell.h"
 
 FLEXTableViewCellReuseIdentifier const kFLEXDefaultCell = @"kFLEXDefaultCell";
 FLEXTableViewCellReuseIdentifier const kFLEXDetailCell = @"kFLEXDetailCell";
 FLEXTableViewCellReuseIdentifier const kFLEXMultilineCell = @"kFLEXMultilineCell";
 FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell = @"kFLEXMultilineDetailCell";
+FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell = @"kFLEXCodeFontCell";
 
 #pragma mark Private
 
@@ -69,6 +71,10 @@ FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell = @"kFLEXMultili
     return [[self alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
 }
 
++ (id)style:(UITableViewStyle)style {
+    return [[self alloc] initWithFrame:CGRectZero style:style];
+}
+
 - (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
     self = [super initWithFrame:frame style:style];
     if (self) {
@@ -76,7 +82,8 @@ FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell = @"kFLEXMultili
             kFLEXDefaultCell : [FLEXTableViewCell class],
             kFLEXDetailCell : [FLEXSubtitleTableViewCell class],
             kFLEXMultilineCell : [FLEXMultilineTableViewCell class],
-            kFLEXMultilineDetailCell : [FLEXMultilineDetailTableViewCell class]
+            kFLEXMultilineDetailCell : [FLEXMultilineDetailTableViewCell class],
+            kFLEXCodeFontCell : [FLEXCodeFontCell class],
         }];
     }
 

+ 5 - 0
Classes/ObjectExplorers/Views/FLEXTableViewCell.h

@@ -15,4 +15,9 @@
 /// Use this instead of .detailTextLabel
 @property (nonatomic, readonly) UILabel *subtitleLabel;
 
+/// Subclasses can override this instead of initializers to
+/// perform additional initialization without lots of boilerplate.
+/// Remember to call super!
+- (void)postInit;
+
 @end

+ 11 - 7
Classes/ObjectExplorers/Views/FLEXTableViewCell.m

@@ -28,18 +28,22 @@
 {
     self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
     if (self) {
-        UIFont *cellFont = UIFont.flex_defaultTableCellFont;
-        self.titleLabel.font = cellFont;
-        self.subtitleLabel.font = cellFont;
-        self.subtitleLabel.textColor = [FLEXColor deemphasizedTextColor];
-        
-        self.titleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
-        self.subtitleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+        [self postInit];
     }
 
     return self;
 }
 
+- (void)postInit {
+    UIFont *cellFont = UIFont.flex_defaultTableCellFont;
+    self.titleLabel.font = cellFont;
+    self.subtitleLabel.font = cellFont;
+    self.subtitleLabel.textColor = [FLEXColor deemphasizedTextColor];
+    
+    self.titleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+    self.subtitleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+}
+
 - (UILabel *)titleLabel {
     return self.textLabel;
 }

+ 29 - 0
Classes/Utility/Categories/NSObject+Reflection.m

@@ -8,11 +8,13 @@
 //
 
 #import "NSObject+Reflection.h"
+#import "FLEXClassBuilder.h"
 #import "FLEXMirror.h"
 #import "FLEXProperty.h"
 #import "FLEXMethod.h"
 #import "FLEXIvar.h"
 #import "FLEXPropertyAttributes.h"
+#import "NSArray+Functional.h"
 
 
 NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...) {
@@ -32,6 +34,33 @@ NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...)
     return encoding.copy;
 }
 
+
+#pragma mark NSProxy
+
+@interface NSProxy (AnyObjectAdditions) @end
+@implementation NSProxy (AnyObjectAdditions)
+
++ (void)load {
+    // We need to get all of the methods in this file and add them to NSProxy. 
+    // To do this we we need the class itself and it's metaclass.
+    Class NSProxyClass = [NSProxy class];
+    Class NSProxy_meta = object_getClass(NSProxyClass);
+    
+    // Copy all of the "flex_" methods from NSObject
+    id filterFunc = ^BOOL(FLEXMethod *method, NSUInteger idx) {
+        return [method.name hasPrefix:@"flex_"];
+    };
+    NSArray *instanceMethods = [[NSObject flex_allInstanceMethods] flex_filtered:filterFunc];
+    NSArray *classMethods = [[NSObject flex_allClassMethods] flex_filtered:filterFunc];
+    
+    FLEXClassBuilder *proxy = [FLEXClassBuilder builderForClass:NSProxyClass];
+    FLEXClassBuilder *meta  = [FLEXClassBuilder builderForClass:NSProxy_meta];
+    [proxy addMethods:instanceMethods];
+    [meta addMethods:classMethods];
+}
+
+@end
+
 #pragma mark Reflection
 
 @implementation NSObject (Reflection)

+ 1 - 1
Classes/Utility/Categories/NSString+KeyPaths.h

@@ -1,6 +1,6 @@
 //
 //  NSString+KeyPaths.h
-//  TBTweakViewController
+//  FLEX
 //
 //  Created by Tanner on 3/26/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.

+ 1 - 1
Classes/Utility/Categories/NSString+KeyPaths.m

@@ -1,6 +1,6 @@
 //
 //  NSString+KeyPaths.m
-//  TBTweakViewController
+//  FLEX
 //
 //  Created by Tanner on 3/26/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.

+ 18 - 0
Classes/Utility/Categories/NSTimer+Blocks.h

@@ -0,0 +1,18 @@
+//
+//  NSTimer+Blocks.h
+//  FLEX
+//
+//  Created by Tanner on 3/23/17.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef void (^VoidBlock)();
+@interface NSTimer (Blocks)
+
++ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
+
+// Forward declaration
+//+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
+
+@end

+ 25 - 0
Classes/Utility/Categories/NSTimer+Blocks.m

@@ -0,0 +1,25 @@
+//
+//  NSTimer+Blocks.m
+//  FLEX
+//
+//  Created by Tanner on 3/23/17.
+//
+
+#import "NSTimer+Blocks.h"
+
+@interface Block : NSObject
+- (void)invoke;
+@end
+
+#pragma clang diagnostic ignored "-Wincomplete-implementation"
+@implementation NSTimer (Blocks)
+
++ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block {
+    if (@available(iOS 10, *)) {
+        return [self scheduledTimerWithTimeInterval:delay repeats:NO block:block];
+    } else {
+        return [self scheduledTimerWithTimeInterval:delay target:block selector:@selector(invoke) userInfo:nil repeats:NO];
+    }
+}
+
+@end

+ 14 - 0
Classes/Utility/Categories/UITextField+Range.h

@@ -0,0 +1,14 @@
+//
+//  UITextField+Range.h
+//  FLEX
+//
+//  Created by Tanner on 6/13/17.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UITextField (Range)
+
+@property (nonatomic, readonly) NSRange selectedRange;
+
+@end

+ 23 - 0
Classes/Utility/Categories/UITextField+Range.m

@@ -0,0 +1,23 @@
+//
+//  UITextField+Range.m
+//  FLEX
+//
+//  Created by Tanner on 6/13/17.
+//
+
+#import "UITextField+Range.h"
+
+@implementation UITextField (Range)
+
+- (NSRange)selectedRange {
+    UITextRange *r = self.selectedTextRange;
+    if (r) {
+        NSInteger loc = [self offsetFromPosition:self.beginningOfDocument toPosition:r.start];
+        NSInteger len = [self offsetFromPosition:r.start toPosition:r.end];
+        return NSMakeRange(loc, len);
+    }
+
+    return NSMakeRange(NSNotFound, 0);
+}
+
+@end

+ 9 - 1
Classes/Utility/Runtime/FLEXMethod.m

@@ -113,13 +113,21 @@
 }
 
 - (NSArray *)prettyArgumentComponents {
+    // NSMethodSignature can't handle some type encodings
+    // like ^AI@:ir* which happen to very much exist
+    if (self.signature.numberOfArguments < self.numberOfArguments) {
+        return nil;
+    }
+    
     NSMutableArray *components = [NSMutableArray new];
 
     NSArray *selectorComponents = [self.selectorString componentsSeparatedByString:@":"];
     NSUInteger numberOfArguments = self.numberOfArguments;
     
     for (NSUInteger argIndex = 2; argIndex < numberOfArguments; argIndex++) {
-        const char *argType = [self.signature getArgumentTypeAtIndex:argIndex];
+        assert(argIndex < self.signature.numberOfArguments);
+        
+        const char *argType = [self.signature getArgumentTypeAtIndex:argIndex] ?: "?";
         NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)];
         NSString *prettyComponent = [NSString
             stringWithFormat:@"%@:(%@) ",

+ 27 - 0
Classes/Utility/Runtime/FLEXRuntimeSafety.h

@@ -0,0 +1,27 @@
+//
+//  FLEXRuntimeSafety.h
+//  FLEX
+//
+//  Created by Tanner on 3/25/17.
+//
+
+#import <Foundation/Foundation.h>
+
+
+extern NSUInteger const kFLEXKnownUnsafeClassCount;
+extern const Class * FLEXKnownUnsafeClassList();
+extern NSSet * FLEXKnownUnsafeClassNames();
+extern CFSetRef FLEXKnownUnsafeClasses;
+
+static inline BOOL FLEXClassIsSafe(Class cls) {
+    if (!cls) return NO;
+
+    return CFSetContainsValue(FLEXKnownUnsafeClasses, (__bridge void *)cls);
+}
+
+static inline BOOL FLEXClassNameIsSafe(NSString *cls) {
+    if (!cls) return NO;
+    
+    NSSet *ignored = FLEXKnownUnsafeClassNames();
+    return ![ignored containsObject:cls];
+}

+ 92 - 0
Classes/Utility/Runtime/FLEXRuntimeSafety.m

@@ -0,0 +1,92 @@
+//
+//  FLEXRuntimeSafety.m
+//  FLEX
+//
+//  Created by Tanner on 3/25/17.
+//
+
+#import "FLEXRuntimeSafety.h"
+
+NSUInteger const kFLEXKnownUnsafeClassCount = 19;
+Class * _UnsafeClasses = NULL;
+CFSetRef FLEXKnownUnsafeClasses = nil;
+
+#define FLEXClassPointerOrCFNull(name) \
+    (NSClassFromString(name) ?: (__bridge id)kCFNull)
+
+__attribute__((constructor))
+void FLEXRuntimeSafteyInit() {
+    FLEXKnownUnsafeClasses = CFSetCreate(
+        kCFAllocatorDefault,
+        (const void **)(uintptr_t)FLEXKnownUnsafeClassList(),
+        kFLEXKnownUnsafeClassCount,
+        nil
+    );
+}
+
+const Class * FLEXKnownUnsafeClassList() {
+    if (!_UnsafeClasses) {
+        const Class ignored[] = {
+            FLEXClassPointerOrCFNull(@"__ARCLite__"),
+            FLEXClassPointerOrCFNull(@"__NSCFCalendar"),
+            FLEXClassPointerOrCFNull(@"__NSCFTimer"),
+            FLEXClassPointerOrCFNull(@"NSCFTimer"),
+            FLEXClassPointerOrCFNull(@"__NSGenericDeallocHandler"),
+            FLEXClassPointerOrCFNull(@"NSAutoreleasePool"),
+            FLEXClassPointerOrCFNull(@"NSPlaceholderNumber"),
+            FLEXClassPointerOrCFNull(@"NSPlaceholderString"),
+            FLEXClassPointerOrCFNull(@"NSPlaceholderValue"),
+            FLEXClassPointerOrCFNull(@"Object"),
+            FLEXClassPointerOrCFNull(@"VMUArchitecture"),
+            FLEXClassPointerOrCFNull(@"JSExport"),
+            FLEXClassPointerOrCFNull(@"__NSAtom"),
+            FLEXClassPointerOrCFNull(@"_NSZombie_"),
+            FLEXClassPointerOrCFNull(@"_CNZombie_"),
+            FLEXClassPointerOrCFNull(@"__NSMessage"),
+            FLEXClassPointerOrCFNull(@"__NSMessageBuilder"),
+            FLEXClassPointerOrCFNull(@"FigIrisAutoTrimmerMotionSampleExport"),
+            // Temporary until we have our own type encoding parser;
+            // setVectors: has an invalid type encoding and crashes NSMethodSignature
+            FLEXClassPointerOrCFNull(@"_UIPointVector"),
+        };
+        
+        assert((sizeof(ignored) / sizeof(Class)) == kFLEXKnownUnsafeClassCount);
+
+        _UnsafeClasses = (Class *)malloc(sizeof(ignored));
+        memcpy(_UnsafeClasses, ignored, sizeof(ignored));
+    }
+
+    return _UnsafeClasses;
+}
+
+NSSet * FLEXKnownUnsafeClassNames() {
+    static NSSet *set = nil;
+    if (!set) {
+        NSArray *ignored = @[
+            @"__ARCLite__",
+            @"__NSCFCalendar",
+            @"__NSCFTimer",
+            @"NSCFTimer",
+            @"__NSGenericDeallocHandler",
+            @"NSAutoreleasePool",
+            @"NSPlaceholderNumber",
+            @"NSPlaceholderString",
+            @"NSPlaceholderValue",
+            @"Object",
+            @"VMUArchitecture",
+            @"JSExport",
+            @"__NSAtom",
+            @"_NSZombie_",
+            @"_CNZombie_",
+            @"__NSMessage",
+            @"__NSMessageBuilder",
+            @"FigIrisAutoTrimmerMotionSampleExport",
+            @"_UIPointVector",
+        ];
+
+        set = [NSSet setWithArray:ignored];
+        assert(set.count == kFLEXKnownUnsafeClassCount);
+    }
+
+    return set;
+}

+ 41 - 25
FLEX.xcodeproj/project.pbxproj

@@ -20,7 +20,7 @@
 		3A4C94251B5B20570088C3F2 /* FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94241B5B20570088C3F2 /* FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		3A4C94CD1B5B21410088C3F2 /* FLEXGlobalsEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94441B5B21410088C3F2 /* FLEXGlobalsEntry.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C94CE1B5B21410088C3F2 /* FLEXGlobalsEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94451B5B21410088C3F2 /* FLEXGlobalsEntry.m */; };
-		3A4C94D31B5B21410088C3F2 /* FLEXObjectExplorerFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C944A1B5B21410088C3F2 /* FLEXObjectExplorerFactory.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		3A4C94D31B5B21410088C3F2 /* FLEXObjectExplorerFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C944A1B5B21410088C3F2 /* FLEXObjectExplorerFactory.h */; };
 		3A4C94D41B5B21410088C3F2 /* FLEXObjectExplorerFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C944B1B5B21410088C3F2 /* FLEXObjectExplorerFactory.m */; };
 		3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C944C1B5B21410088C3F2 /* FLEXObjectExplorerViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C94D61B5B21410088C3F2 /* FLEXObjectExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C944D1B5B21410088C3F2 /* FLEXObjectExplorerViewController.m */; };
@@ -70,8 +70,6 @@
 		3A4C950A1B5B21410088C3F2 /* FLEXFieldEditorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94851B5B21410088C3F2 /* FLEXFieldEditorView.m */; };
 		3A4C950F1B5B21410088C3F2 /* FLEXMethodCallingViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C948A1B5B21410088C3F2 /* FLEXMethodCallingViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C948B1B5B21410088C3F2 /* FLEXMethodCallingViewController.m */; };
-		3A4C951E1B5B21410088C3F2 /* FLEXClassesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C949B1B5B21410088C3F2 /* FLEXClassesTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C949C1B5B21410088C3F2 /* FLEXClassesTableViewController.m */; };
 		3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C949F1B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C95231B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A01B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m */; };
 		3A4C95241B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -80,8 +78,6 @@
 		3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A41B5B21410088C3F2 /* FLEXGlobalsTableViewController.m */; };
 		3A4C95281B5B21410088C3F2 /* FLEXInstancesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A51B5B21410088C3F2 /* FLEXInstancesTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C95291B5B21410088C3F2 /* FLEXInstancesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A61B5B21410088C3F2 /* FLEXInstancesTableViewController.m */; };
-		3A4C952A1B5B21410088C3F2 /* FLEXLibrariesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A71B5B21410088C3F2 /* FLEXLibrariesTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		3A4C952B1B5B21410088C3F2 /* FLEXLibrariesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94A81B5B21410088C3F2 /* FLEXLibrariesTableViewController.m */; };
 		3A4C952C1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m */; };
 		3A4C952E1B5B21410088C3F2 /* FLEXWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94AB1B5B21410088C3F2 /* FLEXWebViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -190,6 +186,14 @@
 		C36FBFDC230F3B98008D95D5 /* FLEXIvar.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFCA230F3B98008D95D5 /* FLEXIvar.m */; };
 		C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */; };
 		C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */; };
+		C383C3B923B6A62A007A321B /* FLEXRuntimeSafety.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3B723B6A62A007A321B /* FLEXRuntimeSafety.h */; };
+		C383C3BA23B6A62A007A321B /* FLEXRuntimeSafety.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3B823B6A62A007A321B /* FLEXRuntimeSafety.m */; };
+		C383C3BD23B6B398007A321B /* UITextField+Range.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3BB23B6B398007A321B /* UITextField+Range.m */; };
+		C383C3BE23B6B398007A321B /* UITextField+Range.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3BC23B6B398007A321B /* UITextField+Range.h */; };
+		C383C3C123B6B429007A321B /* NSTimer+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3BF23B6B429007A321B /* NSTimer+Blocks.m */; };
+		C383C3C223B6B429007A321B /* NSTimer+Blocks.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3C023B6B429007A321B /* NSTimer+Blocks.h */; };
+		C383C3C523B6BB81007A321B /* FLEXCodeFontCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C383C3C323B6BB81007A321B /* FLEXCodeFontCell.h */; };
+		C383C3C623B6BB81007A321B /* FLEXCodeFontCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C383C3C423B6BB81007A321B /* FLEXCodeFontCell.m */; };
 		C3878DBA23A749960038FDBE /* FLEXVariableEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3878DB823A749960038FDBE /* FLEXVariableEditorViewController.m */; };
 		C3878DBB23A749960038FDBE /* FLEXVariableEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3878DB923A749960038FDBE /* FLEXVariableEditorViewController.h */; };
 		C3878DBC23A749F70038FDBE /* FLEXFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94871B5B21410088C3F2 /* FLEXFieldEditorViewController.m */; };
@@ -207,12 +211,12 @@
 		C398624D23AD6C67007E6793 /* TBKeyPathSearchController.h in Headers */ = {isa = PBXBuildFile; fileRef = C398624323AD6C67007E6793 /* TBKeyPathSearchController.h */; };
 		C398624E23AD6C67007E6793 /* TBKeyPath.m in Sources */ = {isa = PBXBuildFile; fileRef = C398624423AD6C67007E6793 /* TBKeyPath.m */; };
 		C398624F23AD6C67007E6793 /* TBKeyPathTokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = C398624523AD6C67007E6793 /* TBKeyPathTokenizer.h */; };
-		C398625023AD6C67007E6793 /* TBKeyPathViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C398624623AD6C67007E6793 /* TBKeyPathViewController.m */; };
+		C398625023AD6C67007E6793 /* FLEXObjcRuntimeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C398624623AD6C67007E6793 /* FLEXObjcRuntimeViewController.m */; };
 		C398625123AD6C67007E6793 /* TBKeyPath.h in Headers */ = {isa = PBXBuildFile; fileRef = C398624723AD6C67007E6793 /* TBKeyPath.h */; };
 		C398625223AD6C67007E6793 /* TBKeyPathSearchController.m in Sources */ = {isa = PBXBuildFile; fileRef = C398624823AD6C67007E6793 /* TBKeyPathSearchController.m */; };
 		C398625323AD6C67007E6793 /* TBKeyPathTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = C398624923AD6C67007E6793 /* TBKeyPathTokenizer.m */; };
 		C398625423AD6C67007E6793 /* TBKeyPathToolbar.h in Headers */ = {isa = PBXBuildFile; fileRef = C398624A23AD6C67007E6793 /* TBKeyPathToolbar.h */; };
-		C398625523AD6C67007E6793 /* TBKeyPathViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C398624B23AD6C67007E6793 /* TBKeyPathViewController.h */; };
+		C398625523AD6C67007E6793 /* FLEXObjcRuntimeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C398624B23AD6C67007E6793 /* FLEXObjcRuntimeViewController.h */; };
 		C398625623AD6C67007E6793 /* TBKeyPathToolbar.m in Sources */ = {isa = PBXBuildFile; fileRef = C398624C23AD6C67007E6793 /* TBKeyPathToolbar.m */; };
 		C398625923AD6C88007E6793 /* TBToken.h in Headers */ = {isa = PBXBuildFile; fileRef = C398625723AD6C88007E6793 /* TBToken.h */; };
 		C398625A23AD6C88007E6793 /* TBToken.m in Sources */ = {isa = PBXBuildFile; fileRef = C398625823AD6C88007E6793 /* TBToken.m */; };
@@ -352,8 +356,6 @@
 		3A4C94871B5B21410088C3F2 /* FLEXFieldEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFieldEditorViewController.m; sourceTree = "<group>"; };
 		3A4C948A1B5B21410088C3F2 /* FLEXMethodCallingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMethodCallingViewController.h; sourceTree = "<group>"; };
 		3A4C948B1B5B21410088C3F2 /* FLEXMethodCallingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMethodCallingViewController.m; sourceTree = "<group>"; };
-		3A4C949B1B5B21410088C3F2 /* FLEXClassesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXClassesTableViewController.h; sourceTree = "<group>"; };
-		3A4C949C1B5B21410088C3F2 /* FLEXClassesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXClassesTableViewController.m; sourceTree = "<group>"; };
 		3A4C949F1B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFileBrowserSearchOperation.h; sourceTree = "<group>"; };
 		3A4C94A01B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFileBrowserSearchOperation.m; sourceTree = "<group>"; };
 		3A4C94A11B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFileBrowserTableViewController.h; sourceTree = "<group>"; };
@@ -362,8 +364,6 @@
 		3A4C94A41B5B21410088C3F2 /* FLEXGlobalsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXGlobalsTableViewController.m; sourceTree = "<group>"; };
 		3A4C94A51B5B21410088C3F2 /* FLEXInstancesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXInstancesTableViewController.h; sourceTree = "<group>"; };
 		3A4C94A61B5B21410088C3F2 /* FLEXInstancesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXInstancesTableViewController.m; sourceTree = "<group>"; };
-		3A4C94A71B5B21410088C3F2 /* FLEXLibrariesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXLibrariesTableViewController.h; sourceTree = "<group>"; };
-		3A4C94A81B5B21410088C3F2 /* FLEXLibrariesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXLibrariesTableViewController.m; sourceTree = "<group>"; };
 		3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXLiveObjectsTableViewController.h; sourceTree = "<group>"; };
 		3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXLiveObjectsTableViewController.m; sourceTree = "<group>"; };
 		3A4C94AB1B5B21410088C3F2 /* FLEXWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXWebViewController.h; sourceTree = "<group>"; };
@@ -476,6 +476,14 @@
 		C36FBFCA230F3B98008D95D5 /* FLEXIvar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXIvar.m; sourceTree = "<group>"; };
 		C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjcInternal.h; sourceTree = "<group>"; };
 		C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEXObjcInternal.mm; sourceTree = "<group>"; };
+		C383C3B723B6A62A007A321B /* FLEXRuntimeSafety.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXRuntimeSafety.h; sourceTree = "<group>"; };
+		C383C3B823B6A62A007A321B /* FLEXRuntimeSafety.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXRuntimeSafety.m; sourceTree = "<group>"; };
+		C383C3BB23B6B398007A321B /* UITextField+Range.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+Range.m"; sourceTree = "<group>"; };
+		C383C3BC23B6B398007A321B /* UITextField+Range.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+Range.h"; sourceTree = "<group>"; };
+		C383C3BF23B6B429007A321B /* NSTimer+Blocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+Blocks.m"; sourceTree = "<group>"; };
+		C383C3C023B6B429007A321B /* NSTimer+Blocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+Blocks.h"; sourceTree = "<group>"; };
+		C383C3C323B6BB81007A321B /* FLEXCodeFontCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCodeFontCell.h; sourceTree = "<group>"; };
+		C383C3C423B6BB81007A321B /* FLEXCodeFontCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXCodeFontCell.m; sourceTree = "<group>"; };
 		C3878DB823A749960038FDBE /* FLEXVariableEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXVariableEditorViewController.m; sourceTree = "<group>"; };
 		C3878DB923A749960038FDBE /* FLEXVariableEditorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXVariableEditorViewController.h; sourceTree = "<group>"; };
 		C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCarouselCell.h; sourceTree = "<group>"; };
@@ -491,12 +499,12 @@
 		C398624323AD6C67007E6793 /* TBKeyPathSearchController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBKeyPathSearchController.h; sourceTree = "<group>"; };
 		C398624423AD6C67007E6793 /* TBKeyPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBKeyPath.m; sourceTree = "<group>"; };
 		C398624523AD6C67007E6793 /* TBKeyPathTokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBKeyPathTokenizer.h; sourceTree = "<group>"; };
-		C398624623AD6C67007E6793 /* TBKeyPathViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBKeyPathViewController.m; sourceTree = "<group>"; };
+		C398624623AD6C67007E6793 /* FLEXObjcRuntimeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXObjcRuntimeViewController.m; sourceTree = "<group>"; };
 		C398624723AD6C67007E6793 /* TBKeyPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBKeyPath.h; sourceTree = "<group>"; };
 		C398624823AD6C67007E6793 /* TBKeyPathSearchController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBKeyPathSearchController.m; sourceTree = "<group>"; };
 		C398624923AD6C67007E6793 /* TBKeyPathTokenizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBKeyPathTokenizer.m; sourceTree = "<group>"; };
 		C398624A23AD6C67007E6793 /* TBKeyPathToolbar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBKeyPathToolbar.h; sourceTree = "<group>"; };
-		C398624B23AD6C67007E6793 /* TBKeyPathViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBKeyPathViewController.h; sourceTree = "<group>"; };
+		C398624B23AD6C67007E6793 /* FLEXObjcRuntimeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXObjcRuntimeViewController.h; sourceTree = "<group>"; };
 		C398624C23AD6C67007E6793 /* TBKeyPathToolbar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBKeyPathToolbar.m; sourceTree = "<group>"; };
 		C398625723AD6C88007E6793 /* TBToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBToken.h; sourceTree = "<group>"; };
 		C398625823AD6C88007E6793 /* TBToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBToken.m; sourceTree = "<group>"; };
@@ -757,14 +765,10 @@
 				779B1EBF1C0C4D7C001F5E49 /* DatabaseBrowser */,
 				3A4C94AD1B5B21410088C3F2 /* SystemLog */,
 				C33CF16B22D664E600F9C6C0 /* FileBrowser */,
-				3A4C949B1B5B21410088C3F2 /* FLEXClassesTableViewController.h */,
-				3A4C949C1B5B21410088C3F2 /* FLEXClassesTableViewController.m */,
 				3A4C94A51B5B21410088C3F2 /* FLEXInstancesTableViewController.h */,
 				3A4C94A61B5B21410088C3F2 /* FLEXInstancesTableViewController.m */,
 				C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */,
 				C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */,
-				3A4C94A71B5B21410088C3F2 /* FLEXLibrariesTableViewController.h */,
-				3A4C94A81B5B21410088C3F2 /* FLEXLibrariesTableViewController.m */,
 				3A4C94A91B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h */,
 				3A4C94AA1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m */,
 				679F64841BD53B7B00A8C94C /* FLEXCookiesTableViewController.h */,
@@ -921,6 +925,8 @@
 		C36FBFB8230F3B52008D95D5 /* Runtime */ = {
 			isa = PBXGroup;
 			children = (
+				C383C3B723B6A62A007A321B /* FLEXRuntimeSafety.h */,
+				C383C3B823B6A62A007A321B /* FLEXRuntimeSafety.m */,
 				C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */,
 				C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */,
 				3A4C945B1B5B21410088C3F2 /* FLEXRuntimeUtility.h */,
@@ -958,6 +964,10 @@
 				C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */,
 				C398625B23AD6E90007E6793 /* UIFont+FLEX.h */,
 				C398625C23AD6E90007E6793 /* UIFont+FLEX.m */,
+				C383C3BC23B6B398007A321B /* UITextField+Range.h */,
+				C383C3BB23B6B398007A321B /* UITextField+Range.m */,
+				C383C3C023B6B429007A321B /* NSTimer+Blocks.h */,
+				C383C3BF23B6B429007A321B /* NSTimer+Blocks.m */,
 				C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */,
 				C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */,
 				C3F977812311B38F0032776D /* NSObject+Reflection.h */,
@@ -1005,8 +1015,8 @@
 				C398624923AD6C67007E6793 /* TBKeyPathTokenizer.m */,
 				C398624A23AD6C67007E6793 /* TBKeyPathToolbar.h */,
 				C398624C23AD6C67007E6793 /* TBKeyPathToolbar.m */,
-				C398624B23AD6C67007E6793 /* TBKeyPathViewController.h */,
-				C398624623AD6C67007E6793 /* TBKeyPathViewController.m */,
+				C398624B23AD6C67007E6793 /* FLEXObjcRuntimeViewController.h */,
+				C398624623AD6C67007E6793 /* FLEXObjcRuntimeViewController.m */,
 				C398625F23AD70DF007E6793 /* TBKeyboardToolbar.h */,
 				C398626023AD70DF007E6793 /* TBKeyboardToolbar.m */,
 				C398626323AD70F5007E6793 /* TBToolbarButton.h */,
@@ -1082,6 +1092,8 @@
 				C3F31D372267D883003C991A /* FLEXSubtitleTableViewCell.m */,
 				C3F31D362267D883003C991A /* FLEXMultilineTableViewCell.h */,
 				C3F31D382267D883003C991A /* FLEXMultilineTableViewCell.m */,
+				C383C3C323B6BB81007A321B /* FLEXCodeFontCell.h */,
+				C383C3C423B6BB81007A321B /* FLEXCodeFontCell.m */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -1101,10 +1113,10 @@
 				C398624D23AD6C67007E6793 /* TBKeyPathSearchController.h in Headers */,
 				3A4C95381B5B21410088C3F2 /* FLEXNetworkRecorder.h in Headers */,
 				3A4C94251B5B20570088C3F2 /* FLEX.h in Headers */,
+				C383C3C523B6BB81007A321B /* FLEXCodeFontCell.h in Headers */,
 				C3F527C12318670F009CBA07 /* FLEXImageShortcuts.h in Headers */,
 				3A4C95051B5B21410088C3F2 /* FLEXArgumentInputViewFactory.h in Headers */,
 				222C88221C7339DC007CA15F /* FLEXRealmDefines.h in Headers */,
-				3A4C951E1B5B21410088C3F2 /* FLEXClassesTableViewController.h in Headers */,
 				779B1ED21C0C4D7C001F5E49 /* FLEXTableColumnHeader.h in Headers */,
 				3A4C94FD1B5B21410088C3F2 /* FLEXArgumentInputStructView.h in Headers */,
 				C3F646C1239EAA8F00D4A011 /* UIPasteboard+FLEX.h in Headers */,
@@ -1112,7 +1124,7 @@
 				C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
 				3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */,
 				3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */,
-				C398625523AD6C67007E6793 /* TBKeyPathViewController.h in Headers */,
+				C398625523AD6C67007E6793 /* FLEXObjcRuntimeViewController.h in Headers */,
 				C3E5DA02231700EE00E655DB /* FLEXExplorerSection.h in Headers */,
 				C34D4EB023A2ABD900C1F903 /* FLEXLayerShortcuts.h in Headers */,
 				C3F646F623A04A7500D4A011 /* FLEXShortcut.h in Headers */,
@@ -1173,7 +1185,6 @@
 				C32A19622317378C00EB02AC /* FLEXDefaultsContentSection.h in Headers */,
 				3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */,
 				3A4C95011B5B21410088C3F2 /* FLEXArgumentInputTextView.h in Headers */,
-				3A4C952A1B5B21410088C3F2 /* FLEXLibrariesTableViewController.h in Headers */,
 				C36FBFCC230F3B98008D95D5 /* FLEXProtocolBuilder.h in Headers */,
 				C36FBFDB230F3B98008D95D5 /* FLEXClassBuilder.h in Headers */,
 				779B1ED41C0C4D7C001F5E49 /* FLEXTableContentCell.h in Headers */,
@@ -1190,6 +1201,7 @@
 				94A515251C4CA2080063292F /* FLEXExplorerToolbar.h in Headers */,
 				C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */,
 				3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */,
+				C383C3C223B6B429007A321B /* NSTimer+Blocks.h in Headers */,
 				3A4C952E1B5B21410088C3F2 /* FLEXWebViewController.h in Headers */,
 				C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */,
 				C398627223AD7951007E6793 /* UIGestureRecognizer+Blocks.h in Headers */,
@@ -1199,6 +1211,7 @@
 				C398625923AD6C88007E6793 /* TBToken.h in Headers */,
 				C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */,
 				C34D4EB423A2AF2A00C1F903 /* FLEXColorPreviewSection.h in Headers */,
+				C383C3BE23B6B398007A321B /* UITextField+Range.h in Headers */,
 				224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
 				C398682623AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h in Headers */,
 				7349FD6A22B93CDF00051810 /* FLEXColor.h in Headers */,
@@ -1220,6 +1233,7 @@
 				C398626123AD70DF007E6793 /* TBKeyboardToolbar.h in Headers */,
 				94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */,
 				94A515181C4CA1D70063292F /* FLEXManager+Private.h in Headers */,
+				C383C3B923B6A62A007A321B /* FLEXRuntimeSafety.h in Headers */,
 				C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */,
 				C32A195E231732E800EB02AC /* FLEXCollectionContentSection.h in Headers */,
 				3A4C94E11B5B21410088C3F2 /* FLEXResources.h in Headers */,
@@ -1356,12 +1370,12 @@
 				C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */,
 				3A4C94F61B5B21410088C3F2 /* FLEXArgumentInputObjectView.m in Sources */,
 				3A4C94EC1B5B21410088C3F2 /* FLEXImagePreviewViewController.m in Sources */,
+				C383C3BA23B6A62A007A321B /* FLEXRuntimeSafety.m in Sources */,
 				3A4C94F21B5B21410088C3F2 /* FLEXArgumentInputFontsPickerView.m in Sources */,
 				C3BFD071233C23ED0015FB82 /* NSArray+Functional.m in Sources */,
 				7349FD6B22B93CDF00051810 /* FLEXColor.m in Sources */,
 				C3878DBA23A749960038FDBE /* FLEXVariableEditorViewController.m in Sources */,
 				94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */,
-				3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */,
 				C398624E23AD6C67007E6793 /* TBKeyPath.m in Sources */,
 				C36FBFD4230F3B98008D95D5 /* FLEXProtocol.m in Sources */,
 				C34D4EB123A2ABD900C1F903 /* FLEXLayerShortcuts.m in Sources */,
@@ -1402,6 +1416,7 @@
 				3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m in Sources */,
 				C3F527BE2318603F009CBA07 /* FLEXShortcutsSection.m in Sources */,
 				3A4C94D61B5B21410088C3F2 /* FLEXObjectExplorerViewController.m in Sources */,
+				C383C3BD23B6B398007A321B /* UITextField+Range.m in Sources */,
 				3A4C94DE1B5B21410088C3F2 /* FLEXHeapEnumerator.m in Sources */,
 				3A4C95251B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m in Sources */,
 				C36FBFD1230F3B98008D95D5 /* FLEXClassBuilder.m in Sources */,
@@ -1415,6 +1430,7 @@
 				3A4C94FC1B5B21410088C3F2 /* FLEXArgumentInputStringView.m in Sources */,
 				3A4C94F81B5B21410088C3F2 /* FLEXArgumentInputNotSupportedView.m in Sources */,
 				3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
+				C383C3C623B6BB81007A321B /* FLEXCodeFontCell.m in Sources */,
 				3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
 				71E1C2172307FBB800F5032A /* FLEXKeychain.m in Sources */,
 				C3F646F323A045DB00D4A011 /* FLEXClassShortcuts.m in Sources */,
@@ -1432,6 +1448,7 @@
 				C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
 				94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,
 				C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */,
+				C383C3C123B6B429007A321B /* NSTimer+Blocks.m in Sources */,
 				3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
 				779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
 				C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */,
@@ -1448,14 +1465,13 @@
 				C34D4EB923A2B17900C1F903 /* FLEXBundleShortcuts.m in Sources */,
 				3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */,
 				C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */,
-				C398625023AD6C67007E6793 /* TBKeyPathViewController.m in Sources */,
+				C398625023AD6C67007E6793 /* FLEXObjcRuntimeViewController.m in Sources */,
 				C398626E23AD71C1007E6793 /* TBRuntime.m in Sources */,
 				3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */,
 				3A4C94E81B5B21410088C3F2 /* FLEXHierarchyTableViewCell.m in Sources */,
 				3A4C950A1B5B21410088C3F2 /* FLEXFieldEditorView.m in Sources */,
 				3A4C95061B5B21410088C3F2 /* FLEXArgumentInputViewFactory.m in Sources */,
 				3A4C95291B5B21410088C3F2 /* FLEXInstancesTableViewController.m in Sources */,
-				3A4C952B1B5B21410088C3F2 /* FLEXLibrariesTableViewController.m in Sources */,
 				71E1C2182307FBB800F5032A /* FLEXKeychainTableViewController.m in Sources */,
 				94A5151E1C4CA1F10063292F /* FLEXExplorerViewController.m in Sources */,
 				C398625A23AD6C88007E6793 /* TBToken.m in Sources */,