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

Refactor the globals view controller list

FLEXGlobalsTableViewControllerEntry now has a method to be initialized with a class conforming to a new protocol, FLEXGlobalsTableViewControllerEntry, which itself provides the properties the entry needs. This removes a lot of boilerplate from the globals view controller itself.
Tanner Bennett пре 7 година
родитељ
комит
c3bb4ff0d3
20 измењених фајлова са 360 додато и 253 уклоњено
  1. 13 0
      Classes/GlobalStateExplorers/FLEXAddressExplorerCoordinator.h
  2. 103 0
      Classes/GlobalStateExplorers/FLEXAddressExplorerCoordinator.m
  3. 2 1
      Classes/GlobalStateExplorers/FLEXClassesTableViewController.h
  4. 14 0
      Classes/GlobalStateExplorers/FLEXClassesTableViewController.m
  5. 2 2
      Classes/GlobalStateExplorers/FLEXCookiesTableViewController.h
  6. 10 0
      Classes/GlobalStateExplorers/FLEXCookiesTableViewController.m
  7. 80 243
      Classes/GlobalStateExplorers/FLEXGlobalsTableViewController.m
  8. 2 1
      Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.h
  9. 14 0
      Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.m
  10. 2 1
      Classes/GlobalStateExplorers/FLEXLiveObjectsTableViewController.h
  11. 11 0
      Classes/GlobalStateExplorers/FLEXLiveObjectsTableViewController.m
  12. 2 1
      Classes/GlobalStateExplorers/FileBrowser/FLEXFileBrowserTableViewController.h
  13. 10 0
      Classes/GlobalStateExplorers/FileBrowser/FLEXFileBrowserTableViewController.m
  14. 2 1
      Classes/GlobalStateExplorers/SystemLog/FLEXSystemLogTableViewController.h
  15. 10 0
      Classes/GlobalStateExplorers/SystemLog/FLEXSystemLogTableViewController.m
  16. 2 1
      Classes/Network/FLEXNetworkHistoryTableViewController.h
  17. 10 0
      Classes/Network/FLEXNetworkHistoryTableViewController.m
  18. 28 0
      Classes/ObjectExplorers/Controllers/FLEXGlobalsTableViewControllerEntry.h
  19. 35 2
      Classes/ObjectExplorers/Controllers/FLEXGlobalsTableViewControllerEntry.m
  20. 8 0
      FLEX.xcodeproj/project.pbxproj

+ 13 - 0
Classes/GlobalStateExplorers/FLEXAddressExplorerCoordinator.h

@@ -0,0 +1,13 @@
+//
+//  FLEXAddressExplorerCoordinator.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/10/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXGlobalsTableViewControllerEntry.h"
+
+@interface FLEXAddressExplorerCoordinator : NSObject <FLEXGlobalsTableViewControllerEntry>
+
+@end

+ 103 - 0
Classes/GlobalStateExplorers/FLEXAddressExplorerCoordinator.m

@@ -0,0 +1,103 @@
+//
+//  FLEXAddressExplorerCoordinator.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/10/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXAddressExplorerCoordinator.h"
+#import "FLEXGlobalsTableViewController.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXObjectExplorerViewController.h"
+#import "FLEXRuntimeUtility.h"
+#import "FLEXUtility.h"
+
+@interface FLEXGlobalsTableViewController (FLEXAddressExploration)
+- (void)deselectSelectedRow;
+- (void)tryExploreAddress:(NSString *)addressString safely:(BOOL)safely;
+@end
+
+@implementation FLEXAddressExplorerCoordinator
+
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return @"🔎 Address Explorer";
+}
+
++ (FLEXGlobalsTableViewControllerRowAction)globalsEntryRowAction {
+    return ^(FLEXGlobalsTableViewController *host) {
+        NSString *title = @"Explore Object at Address";
+        NSString *message = @"Paste a hexadecimal address below, starting with '0x'. "
+        "Use the unsafe option if you need to bypass pointer validation, "
+        "but know that it may crash the app if the address is invalid.";
+
+        UIAlertController *addressInput = [UIAlertController alertControllerWithTitle:title
+                                                                              message:message
+                                                                       preferredStyle:UIAlertControllerStyleAlert];
+        void (^handler)(UIAlertAction *) = ^(UIAlertAction *action) {
+            if (action.style == UIAlertActionStyleCancel) {
+                [host deselectSelectedRow]; return;
+            }
+            NSString *address = addressInput.textFields.firstObject.text;
+            [host tryExploreAddress:address safely:action.style == UIAlertActionStyleDefault];
+        };
+        [addressInput addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+            NSString *copied = [UIPasteboard generalPasteboard].string;
+            textField.placeholder = @"0x00000070deadbeef";
+            // Go ahead and paste our clipboard if we have an address copied
+            if ([copied hasPrefix:@"0x"]) {
+                textField.text = copied;
+                [textField selectAll:nil];
+            }
+        }];
+        [addressInput addAction:[UIAlertAction actionWithTitle:@"Explore"
+                                                         style:UIAlertActionStyleDefault
+                                                       handler:handler]];
+        [addressInput addAction:[UIAlertAction actionWithTitle:@"Unsafe Explore"
+                                                         style:UIAlertActionStyleDestructive
+                                                       handler:handler]];
+        [addressInput addAction:[UIAlertAction actionWithTitle:@"Cancel"
+                                                         style:UIAlertActionStyleCancel
+                                                       handler:handler]];
+        [host presentViewController:addressInput animated:YES completion:nil];
+    };
+}
+
+@end
+
+@implementation FLEXGlobalsTableViewController (FLEXAddressExploration)
+
+- (void)deselectSelectedRow {
+    NSIndexPath *selected = self.tableView.indexPathForSelectedRow;
+    [self.tableView deselectRowAtIndexPath:selected animated:YES];
+}
+
+- (void)tryExploreAddress:(NSString *)addressString safely:(BOOL)safely {
+    NSScanner *scanner = [NSScanner scannerWithString:addressString];
+    unsigned long long hexValue = 0;
+    BOOL didParseAddress = [scanner scanHexLongLong:&hexValue];
+    const void *pointerValue = (void *)hexValue;
+
+    NSString *error = nil;
+
+    if (didParseAddress) {
+        if (safely && ![FLEXRuntimeUtility pointerIsValidObjcObject:pointerValue]) {
+            error = @"The given address is unlikely to be a valid object.";
+        }
+    } else {
+        error = @"Malformed address. Make sure it's not too long and starts with '0x'.";
+    }
+
+    if (!error) {
+        id object = (__bridge id)pointerValue;
+        FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:object];
+        [self.navigationController pushViewController:explorer animated:YES];
+    } else {
+        [FLEXUtility alert:@"Uh-oh" message:error from:self];
+        [self deselectSelectedRow];
+    }
+}
+
+@end

+ 2 - 1
Classes/GlobalStateExplorers/FLEXClassesTableViewController.h

@@ -7,8 +7,9 @@
 //
 
 #import "FLEXTableViewController.h"
+#import "FLEXGlobalsTableViewControllerEntry.h"
 
-@interface FLEXClassesTableViewController : FLEXTableViewController
+@interface FLEXClassesTableViewController : FLEXTableViewController <FLEXGlobalsTableViewControllerEntry>
 
 @property (nonatomic, copy) NSString *binaryImageName;
 

+ 14 - 0
Classes/GlobalStateExplorers/FLEXClassesTableViewController.m

@@ -68,6 +68,20 @@
 }
 
 
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return [NSString stringWithFormat:@"📕  %@ Classes", [FLEXUtility applicationName]];
+}
+
++ (instancetype)globalsEntryViewController {
+    FLEXClassesTableViewController *classesViewController = [self new];
+    classesViewController.binaryImageName = [FLEXUtility applicationImageName];
+
+    return classesViewController;
+}
+
+
 #pragma mark - Search bar
 
 - (void)updateSearchResults:(NSString *)searchText

+ 2 - 2
Classes/GlobalStateExplorers/FLEXCookiesTableViewController.h

@@ -6,8 +6,8 @@
 //  Copyright © 2015 Flipboard. All rights reserved.
 //
 
-#import <UIKit/UIKit.h>
+#import "FLEXGlobalsTableViewControllerEntry.h"
 
-@interface FLEXCookiesTableViewController : UITableViewController
+@interface FLEXCookiesTableViewController : UITableViewController <FLEXGlobalsTableViewControllerEntry>
 
 @end

+ 10 - 0
Classes/GlobalStateExplorers/FLEXCookiesTableViewController.m

@@ -68,4 +68,14 @@
     [self.navigationController pushViewController:cookieViewController animated:YES];
 }
 
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return @"🍪  Cookies";
+}
+
++ (instancetype)globalsEntryViewController {
+    return [self new];
+}
+
 @end

+ 80 - 243
Classes/GlobalStateExplorers/FLEXGlobalsTableViewController.m

@@ -20,6 +20,7 @@
 #import "FLEXManager+Private.h"
 #import "FLEXSystemLogTableViewController.h"
 #import "FLEXNetworkHistoryTableViewController.h"
+#import "FLEXAddressExplorerCoordinator.h"
 
 static __weak UIWindow *s_applicationWindow = nil;
 
@@ -51,240 +52,107 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
 
 @implementation FLEXGlobalsTableViewController
 
-+ (NSArray<FLEXGlobalsTableViewControllerEntry *> *)defaultGlobalEntries
-{
-    NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *defaultGlobalEntries = [NSMutableArray array];
-
-    for (FLEXGlobalsRow defaultRowIndex = 0; defaultRowIndex < FLEXGlobalsRowCount; defaultRowIndex++) {
-        FLEXGlobalsTableViewControllerEntryNameFuture titleFuture = nil;
-        FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture = nil;
-        FLEXGlobalsTableViewControllerRowAction rowAction = nil;
-
-        switch (defaultRowIndex) {
-            case FLEXGlobalsRowAppClasses: {
-                titleFuture = ^NSString *{
-                    return [NSString stringWithFormat:@"📕  %@ Classes", [FLEXUtility applicationName]];
-                };
-                viewControllerFuture = ^UIViewController *{
-                    FLEXClassesTableViewController *classesViewController = [[FLEXClassesTableViewController alloc] init];
-                    classesViewController.binaryImageName = [FLEXUtility applicationImageName];
-
-                    return classesViewController;
-                };
-                break;
-            }
-                
-            case FLEXGlobalsRowAddressInspector: {
-                titleFuture = ^NSString *{
-                    return @"🔎 Address Explorer";
-                };
-                
-                rowAction = ^(FLEXGlobalsTableViewController *host) {
-                    NSString *title = @"Explore Object at Address";
-                    NSString *message = @"Paste a hexadecimal address below, starting with '0x'. "
-                    "Use the unsafe option if you need to bypass pointer validation, "
-                    "but know that it may crash the app if the address is invalid.";
-
-                    UIAlertController *addressInput = [UIAlertController alertControllerWithTitle:title
-                                                                                          message:message
-                                                                                   preferredStyle:UIAlertControllerStyleAlert];
-                    void (^handler)(UIAlertAction *) = ^(UIAlertAction *action) {
-                        if (action.style == UIAlertActionStyleCancel) {
-                            [host deselectSelectedRow]; return;
-                        }
-                        NSString *address = addressInput.textFields.firstObject.text;
-                        [host tryExploreAddress:address safely:action.style == UIAlertActionStyleDefault];
-                    };
-                    [addressInput addTextFieldWithConfigurationHandler:^(UITextField *textField) {
-                        NSString *copied = [UIPasteboard generalPasteboard].string;
-                        textField.placeholder = @"0x00000070deadbeef";
-                        // Go ahead and paste our clipboard if we have an address copied
-                        if ([copied hasPrefix:@"0x"]) {
-                            textField.text = copied;
-                            [textField selectAll:nil];
-                        }
-                    }];
-                    [addressInput addAction:[UIAlertAction actionWithTitle:@"Explore"
-                                                                     style:UIAlertActionStyleDefault
-                                                                   handler:handler]];
-                    [addressInput addAction:[UIAlertAction actionWithTitle:@"Unsafe Explore"
-                                                                     style:UIAlertActionStyleDestructive
-                                                                   handler:handler]];
-                    [addressInput addAction:[UIAlertAction actionWithTitle:@"Cancel"
-                                                                     style:UIAlertActionStyleCancel
-                                                                   handler:handler]];
-                    [host presentViewController:addressInput animated:YES completion:nil];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowSystemLibraries: {
-                NSString *titleString = @"📚  System Libraries";
-                titleFuture = ^NSString *{
-                    return titleString;
-                };
-                viewControllerFuture = ^UIViewController *{
-                    FLEXLibrariesTableViewController *librariesViewController = [[FLEXLibrariesTableViewController alloc] init];
-                    librariesViewController.title = titleString;
-
-                    return librariesViewController;
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowLiveObjects: {
-                titleFuture = ^NSString *{
-                    return @"💩  Heap Objects";
-                };
-                viewControllerFuture = ^UIViewController *{
-                    return [[FLEXLiveObjectsTableViewController alloc] init];
-                };
-
-                break;
-            }
-
-            case FLEXGlobalsRowAppDelegate: {
-                titleFuture = ^NSString *{
-                    return [NSString stringWithFormat:@"👉  %@", [[[UIApplication sharedApplication] delegate] class]];
-                };
-                viewControllerFuture = ^UIViewController *{
-                    id <UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
++ (FLEXGlobalsTableViewControllerEntry *)globalsEntryForRow:(FLEXGlobalsRow)row {
+    switch (row) {
+        case FLEXGlobalsRowAppClasses:
+            return [FLEXClassesTableViewController flex_concreteGlobalsEntry];
+        case FLEXGlobalsRowAddressInspector:
+            return [FLEXAddressExplorerCoordinator flex_concreteGlobalsEntry];
+        case FLEXGlobalsRowSystemLibraries:
+            return [FLEXLibrariesTableViewController flex_concreteGlobalsEntry];
+        case FLEXGlobalsRowLiveObjects:
+            return [FLEXLiveObjectsTableViewController flex_concreteGlobalsEntry];
+        case FLEXGlobalsCookies:
+            return [FLEXCookiesTableViewController flex_concreteGlobalsEntry];
+        case FLEXGlobalsRowFileBrowser:
+            return [FLEXFileBrowserTableViewController flex_concreteGlobalsEntry];
+        case FLEXGlobalsRowSystemLog:
+            return [FLEXSystemLogTableViewController flex_concreteGlobalsEntry];
+        case FLEXGlobalsRowNetworkHistory:
+            return [FLEXNetworkHistoryTableViewController flex_concreteGlobalsEntry];
+        case FLEXGlobalsRowAppDelegate:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
+                    return [NSString stringWithFormat:@"👉  %@", [[UIApplication sharedApplication].delegate class]];
+                } viewControllerFuture:^UIViewController *{
+                    id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:appDelegate];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowRootViewController: {
-                titleFuture = ^NSString *{
-                    return [NSString stringWithFormat:@"🌴  %@", [[s_applicationWindow rootViewController] class]];
-                };
-                viewControllerFuture = ^UIViewController *{
-                    UIViewController *rootViewController = [s_applicationWindow rootViewController];
+                }
+            ];
+        case FLEXGlobalsRowRootViewController:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
+                    return [NSString stringWithFormat:@"🌴  %@", [s_applicationWindow.rootViewController class]];
+                } viewControllerFuture:^UIViewController *{
+                    UIViewController *rootViewController = s_applicationWindow.rootViewController;
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:rootViewController];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowUserDefaults: {
-                titleFuture = ^NSString *{
+                }
+            ];
+        case FLEXGlobalsRowUserDefaults:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
                     return @"🚶  +[NSUserDefaults standardUserDefaults]";
-                };
-                viewControllerFuture = ^UIViewController *{
+                } viewControllerFuture:^UIViewController *{
                     NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:standardUserDefaults];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowMainBundle: {
-                titleFuture = ^NSString *{
+                }
+            ];
+        case FLEXGlobalsRowMainBundle:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
                     return @"📦  +[NSBundle mainBundle]";
-                };
-                viewControllerFuture = ^UIViewController *{
+                } viewControllerFuture:^UIViewController *{
                     NSBundle *mainBundle = [NSBundle mainBundle];
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:mainBundle];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowApplication: {
-                titleFuture = ^NSString *{
+                }
+            ];
+        case FLEXGlobalsRowApplication:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
                     return @"💾  +[UIApplication sharedApplication]";
-                };
-                viewControllerFuture = ^UIViewController *{
+                } viewControllerFuture:^UIViewController *{
                     UIApplication *sharedApplication = [UIApplication sharedApplication];
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:sharedApplication];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowKeyWindow: {
-                titleFuture = ^NSString *{
+                }
+            ];
+        case FLEXGlobalsRowKeyWindow:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
                     return @"🔑  -[UIApplication keyWindow]";
-                };
-                viewControllerFuture = ^UIViewController *{
+                } viewControllerFuture:^UIViewController *{
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:s_applicationWindow];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowMainScreen: {
-                titleFuture = ^NSString *{
+                }
+            ];
+        case FLEXGlobalsRowMainScreen:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
                     return @"💻  +[UIScreen mainScreen]";
-                };
-                viewControllerFuture = ^UIViewController *{
+                } viewControllerFuture:^UIViewController *{
                     UIScreen *mainScreen = [UIScreen mainScreen];
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:mainScreen];
-                };
-                break;
-            }
+                }
+            ];
 
-            case FLEXGlobalsRowCurrentDevice: {
-                titleFuture = ^NSString *{
+        case FLEXGlobalsRowCurrentDevice:
+            return [FLEXGlobalsTableViewControllerEntry
+                entryWithNameFuture:^NSString *{
                     return @"📱  +[UIDevice currentDevice]";
-                };
-                viewControllerFuture = ^UIViewController *{
+                } viewControllerFuture:^UIViewController *{
                     UIDevice *currentDevice = [UIDevice currentDevice];
                     return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentDevice];
-                };
-                break;
-            }
-
-            case FLEXGlobalsCookies: {
-                titleFuture = ^NSString *{
-                    return @"🍪  Cookies";
-                };
-                viewControllerFuture = ^UIViewController *{
-                    return [[FLEXCookiesTableViewController alloc] init];
-                };
-                break;
-            }
-                
-            case FLEXGlobalsRowFileBrowser: {
-                titleFuture = ^NSString *{
-                    return @"📁  File Browser";
-                };
-                viewControllerFuture = ^UIViewController *{
-                    return [[FLEXFileBrowserTableViewController alloc] init];
-                };
-                break;
-            }
+                }
+            ];
 
-            case FLEXGlobalsRowSystemLog: {
-                titleFuture = ^{
-                    return @"⚠️  System Log";
-                };
-                viewControllerFuture = ^{
-                    return [[FLEXSystemLogTableViewController alloc] init];
-                };
-                break;
-            }
-
-            case FLEXGlobalsRowNetworkHistory: {
-                titleFuture = ^{
-                    return @"📡  Network History";
-                };
-                viewControllerFuture = ^{
-                    return [[FLEXNetworkHistoryTableViewController alloc] init];
-                };
-                break;
-            case FLEXGlobalsRowCount:
-                break;
-            }
-        }
-
-        NSAssert(viewControllerFuture || rowAction, @"The switch-case above must assign one of these");
-
-        if (viewControllerFuture) {
-            [defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry
-                                             entryWithNameFuture:titleFuture
-                                             viewControllerFuture:viewControllerFuture]];
-        } else {
-            [defaultGlobalEntries addObject:[FLEXGlobalsTableViewControllerEntry
-                                             entryWithNameFuture:titleFuture
-                                             action:rowAction]];
-        }
+        default:
+            @throw NSInternalInconsistencyException;
+    }
+}
 
++ (NSArray<FLEXGlobalsTableViewControllerEntry *> *)defaultGlobalEntries
+{
+    NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *defaultGlobalEntries = [NSMutableArray array];
+    for (FLEXGlobalsRow defaultRowIndex = 0; defaultRowIndex < FLEXGlobalsRowCount; defaultRowIndex++) {
+        [defaultGlobalEntries addObject:[self globalsEntryForRow:defaultRowIndex]];
     }
 
     return defaultGlobalEntries;
@@ -300,11 +168,6 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
     return self;
 }
 
-- (void)deselectSelectedRow {
-    NSIndexPath *selected = self.tableView.indexPathForSelectedRow;
-    [self.tableView deselectRowAtIndexPath:selected animated:YES];
-}
-
 #pragma mark - Public
 
 + (void)setApplicationWindow:(UIWindow *)applicationWindow
@@ -328,32 +191,6 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
     [self.delegate globalsViewControllerDidFinish:self];
 }
 
-- (void)tryExploreAddress:(NSString *)addressString safely:(BOOL)safely {
-    NSScanner *scanner = [NSScanner scannerWithString:addressString];
-    unsigned long long hexValue = 0;
-    BOOL didParseAddress = [scanner scanHexLongLong:&hexValue];
-    const void *pointerValue = (void *)hexValue;
-
-    NSString *error = nil;
-
-    if (didParseAddress) {
-        if (safely && ![FLEXRuntimeUtility pointerIsValidObjcObject:pointerValue]) {
-            error = @"The given address is unlikely to be a valid object.";
-        }
-    } else {
-        error = @"Malformed address. Make sure it's not too long and starts with '0x'.";
-    }
-
-    if (!error) {
-        id object = (__bridge id)pointerValue;
-        FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:object];
-        [self.navigationController pushViewController:explorer animated:YES];
-    } else {
-        [FLEXUtility alert:@"Uh-oh" message:error from:self];
-        [self deselectSelectedRow];
-    }
-}
-
 #pragma mark - Table Data Helpers
 
 - (FLEXGlobalsTableViewControllerEntry *)globalEntryAtIndexPath:(NSIndexPath *)indexPath

+ 2 - 1
Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.h

@@ -7,7 +7,8 @@
 //
 
 #import "FLEXTableViewController.h"
+#import "FLEXGlobalsTableViewControllerEntry.h"
 
-@interface FLEXLibrariesTableViewController : FLEXTableViewController
+@interface FLEXLibrariesTableViewController : FLEXTableViewController <FLEXGlobalsTableViewControllerEntry>
 
 @end

+ 14 - 0
Classes/GlobalStateExplorers/FLEXLibrariesTableViewController.m

@@ -40,6 +40,20 @@
 }
 
 
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return @"📚  System Libraries";
+}
+
++ (instancetype)globalsEntryViewController {
+    FLEXLibrariesTableViewController *librariesViewController = [self new];
+    librariesViewController.title = [self globalsEntryTitle];
+
+    return librariesViewController;
+}
+
+
 #pragma mark - Binary Images
 
 - (void)loadImageNames

+ 2 - 1
Classes/GlobalStateExplorers/FLEXLiveObjectsTableViewController.h

@@ -7,7 +7,8 @@
 //
 
 #import "FLEXTableViewController.h"
+#import "FLEXGlobalsTableViewControllerEntry.h"
 
-@interface FLEXLiveObjectsTableViewController : FLEXTableViewController
+@interface FLEXLiveObjectsTableViewController : FLEXTableViewController <FLEXGlobalsTableViewControllerEntry>
 
 @end

+ 11 - 0
Classes/GlobalStateExplorers/FLEXLiveObjectsTableViewController.m

@@ -127,6 +127,17 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
 }
 
 
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return @"💩  Heap Objects";
+}
+
++ (instancetype)globalsEntryViewController {
+    return [self new];
+}
+
+
 #pragma mark - Search bar
 
 - (void)updateSearchResults:(NSString *)newText

+ 2 - 1
Classes/GlobalStateExplorers/FileBrowser/FLEXFileBrowserTableViewController.h

@@ -7,9 +7,10 @@
 //
 
 #import "FLEXTableViewController.h"
+#import "FLEXGlobalsTableViewControllerEntry.h"
 #import "FLEXFileBrowserSearchOperation.h"
 
-@interface FLEXFileBrowserTableViewController : FLEXTableViewController
+@interface FLEXFileBrowserTableViewController : FLEXTableViewController <FLEXGlobalsTableViewControllerEntry>
 
 - (id)initWithPath:(NSString *)path;
 

+ 10 - 0
Classes/GlobalStateExplorers/FileBrowser/FLEXFileBrowserTableViewController.m

@@ -86,6 +86,16 @@
     self.searchBarDebounceInterval = kFLEXDebounceForAsyncSearch;
 }
 
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return @"📁  File Browser";
+}
+
++ (instancetype)globalsEntryViewController {
+    return [self new];
+}
+
 #pragma mark - Misc
 
 - (void)alert:(NSString *)title message:(NSString *)message {

+ 2 - 1
Classes/GlobalStateExplorers/SystemLog/FLEXSystemLogTableViewController.h

@@ -7,7 +7,8 @@
 //
 
 #import "FLEXTableViewController.h"
+#import "FLEXGlobalsTableViewControllerEntry.h"
 
-@interface FLEXSystemLogTableViewController : FLEXTableViewController
+@interface FLEXSystemLogTableViewController : FLEXTableViewController <FLEXGlobalsTableViewControllerEntry>
 
 @end

+ 10 - 0
Classes/GlobalStateExplorers/SystemLog/FLEXSystemLogTableViewController.m

@@ -116,6 +116,16 @@
     [self presentViewController:settings animated:YES completion:nil];
 }
 
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return @"⚠️  System Log";
+}
+
++ (instancetype)globalsEntryViewController {
+    return [self new];
+}
+
 #pragma mark - Table view data source
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

+ 2 - 1
Classes/Network/FLEXNetworkHistoryTableViewController.h

@@ -7,7 +7,8 @@
 //
 
 #import "FLEXTableViewController.h"
+#import "FLEXGlobalsTableViewControllerEntry.h"
 
-@interface FLEXNetworkHistoryTableViewController : FLEXTableViewController
+@interface FLEXNetworkHistoryTableViewController : FLEXTableViewController <FLEXGlobalsTableViewControllerEntry>
 
 @end

+ 10 - 0
Classes/Network/FLEXNetworkHistoryTableViewController.m

@@ -154,6 +154,16 @@
     return headerText;
 }
 
+#pragma mark - FLEXGlobalsTableViewControllerEntry
+
++ (NSString *)globalsEntryTitle {
+    return @"📡  Network History";
+}
+
++ (instancetype)globalsEntryViewController {
+    return [self new];
+}
+
 #pragma mark - Notification Handlers
 
 - (void)handleNewTransactionRecordedNotification:(NSNotification *)notification

+ 28 - 0
Classes/ObjectExplorers/Controllers/FLEXGlobalsTableViewControllerEntry.h

@@ -16,13 +16,41 @@ typedef UIViewController *(^FLEXGlobalsTableViewControllerViewControllerFuture)(
 /// view controller to present or push another view controller.
 typedef void (^FLEXGlobalsTableViewControllerRowAction)(FLEXGlobalsTableViewController *host);
 
+/// For view controllers to conform to to indicate they support being used
+/// in the globals table view controller. These methods help create concrete entries.
+///
+/// Previously, the concrete entries relied on "futures" for the view controller and title.
+/// With this protocol, the conforming class itself can act as a future, since the methods
+/// will not be invoked until the title and view controller / row action are needed.
+@protocol FLEXGlobalsTableViewControllerEntry <NSObject>
+
++ (NSString *)globalsEntryTitle;
+
+// Must respond to at least one of the below
+@optional
+
++ (instancetype)globalsEntryViewController;
++ (FLEXGlobalsTableViewControllerRowAction)globalsEntryRowAction;
+
+@end
+
 @interface FLEXGlobalsTableViewControllerEntry : NSObject
 
 @property (nonatomic, readonly) FLEXGlobalsTableViewControllerEntryNameFuture entryNameFuture;
 @property (nonatomic, readonly) FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture;
 @property (nonatomic, readonly) FLEXGlobalsTableViewControllerRowAction rowAction;
 
++ (instancetype)entryWithEntry:(Class<FLEXGlobalsTableViewControllerEntry>)entry;
 + (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture;
 + (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture action:(FLEXGlobalsTableViewControllerRowAction)rowSelectedAction;
 
 @end
+
+
+@interface NSObject (FLEXGlobalsTableViewControllerEntry)
+
+/// @return The result of passing self to +[FLEXGlobalsTableViewControllerEntry entryWithEntry:]
+/// if the class conforms to FLEXGlobalsTableViewControllerEntry, else, nil.
++ (FLEXGlobalsTableViewControllerEntry *)flex_concreteGlobalsEntry;
+
+@end

+ 35 - 2
Classes/ObjectExplorers/Controllers/FLEXGlobalsTableViewControllerEntry.m

@@ -10,13 +10,33 @@
 
 @implementation FLEXGlobalsTableViewControllerEntry
 
++ (instancetype)entryWithEntry:(Class<FLEXGlobalsTableViewControllerEntry>)cls
+{
+    NSParameterAssert(cls);
+    NSParameterAssert(
+        [cls respondsToSelector:@selector(globalsEntryViewController)] ||
+        [cls respondsToSelector:@selector(globalsEntryRowAction)]
+    );
+
+    FLEXGlobalsTableViewControllerEntry *entry = [self new];
+    entry->_entryNameFuture = ^{ return [cls globalsEntryTitle]; };
+
+    if ([cls respondsToSelector:@selector(globalsEntryViewController)]) {
+        entry->_viewControllerFuture = ^{ return [cls globalsEntryViewController]; };
+    } else {
+        entry->_rowAction = [cls globalsEntryRowAction];
+    }
+
+    return entry;
+}
+
 + (instancetype)entryWithNameFuture:(FLEXGlobalsTableViewControllerEntryNameFuture)nameFuture
                viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture
 {
     NSParameterAssert(nameFuture);
     NSParameterAssert(viewControllerFuture);
 
-    FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
+    FLEXGlobalsTableViewControllerEntry *entry = [self new];
     entry->_entryNameFuture = [nameFuture copy];
     entry->_viewControllerFuture = [viewControllerFuture copy];
 
@@ -29,7 +49,7 @@
     NSParameterAssert(nameFuture);
     NSParameterAssert(rowSelectedAction);
 
-    FLEXGlobalsTableViewControllerEntry *entry = [[self alloc] init];
+    FLEXGlobalsTableViewControllerEntry *entry = [self new];
     entry->_entryNameFuture = [nameFuture copy];
     entry->_rowAction = [rowSelectedAction copy];
 
@@ -37,3 +57,16 @@
 }
 
 @end
+
+
+@implementation NSObject (FLEXGlobalsTableViewControllerEntry)
+
++ (FLEXGlobalsTableViewControllerEntry *)flex_concreteGlobalsEntry {
+    if ([self conformsToProtocol:@protocol(FLEXGlobalsTableViewControllerEntry)]) {
+        return [FLEXGlobalsTableViewControllerEntry entryWithEntry:self];
+    }
+
+    return nil;
+}
+
+@end

+ 8 - 0
FLEX.xcodeproj/project.pbxproj

@@ -178,6 +178,8 @@
 		C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
 		C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */; };
 		C395D6DA21789BD800BEAD4D /* FLEXColorExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */; };
+		C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */; };
+		C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */; };
 		C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */; };
 		C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */; };
 		C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
@@ -381,6 +383,8 @@
 		C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewController.m; sourceTree = "<group>"; };
 		C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorExplorerViewController.h; sourceTree = "<group>"; };
 		C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXColorExplorerViewController.m; sourceTree = "<group>"; };
+		C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXAddressExplorerCoordinator.h; sourceTree = "<group>"; };
+		C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXAddressExplorerCoordinator.m; sourceTree = "<group>"; };
 		C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMutableFieldEditorViewController.h; sourceTree = "<group>"; };
 		C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXMutableFieldEditorViewController.m; sourceTree = "<group>"; };
 		C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectRef.h; sourceTree = "<group>"; };
@@ -608,6 +612,8 @@
 				679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */,
 				3A4C94AB1B5B21410088C3F2 /* FLEXWebViewController.h */,
 				3A4C94AC1B5B21410088C3F2 /* FLEXWebViewController.m */,
+				C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */,
+				C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */,
 			);
 			path = GlobalStateExplorers;
 			sourceTree = "<group>";
@@ -885,6 +891,7 @@
 				94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */,
 				C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */,
 				94A515181C4CA1D70063292F /* FLEXManager+Private.h in Headers */,
+				C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */,
 				3A4C94E11B5B21410088C3F2 /* FLEXResources.h in Headers */,
 				779B1ED81C0C4D7C001F5E49 /* FLEXTableLeftCell.h in Headers */,
 			);
@@ -999,6 +1006,7 @@
 			files = (
 				942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */,
 				224D49A91C673AB5000EAB86 /* FLEXRealmDatabaseManager.m in Sources */,
+				C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */,
 				2EF6B04D1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m in Sources */,
 				94A515201C4CA1F10063292F /* FLEXWindow.m in Sources */,
 				3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */,