Просмотр исходного кода

Refactor FLEXManager into categories for organization

window
Tanner Bennett лет назад: 6
Родитель
Сommit
43e7938281

+ 2 - 3
Classes/ExplorerInterface/FLEXExplorerViewController.h

@@ -14,9 +14,9 @@
 @interface FLEXExplorerViewController : UIViewController
 
 @property (nonatomic, weak) id <FLEXExplorerViewControllerDelegate> delegate;
+@property (nonatomic, readonly) BOOL wantsWindowToBecomeKey;
 
 - (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
-- (BOOL)wantsWindowToBecomeKey;
 
 /// @brief Used to present (or dismiss) a modal view controller ("tool"), typically triggered by pressing a button in the toolbar.
 ///
@@ -37,8 +37,7 @@
 
 @end
 
+#pragma mark -
 @protocol FLEXExplorerViewControllerDelegate <NSObject>
-
 - (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController;
-
 @end

+ 2 - 0
Classes/FLEX.h

@@ -7,3 +7,5 @@
 //
 
 #import <FLEX/FLEXManager.h>
+#import <FLEX/FLEXManager+Extensibility.h>
+#import <FLEX/FLEXManager+Networking.h>

+ 4 - 65
Classes/FLEXManager.h

@@ -6,18 +6,15 @@
 //  Copyright (c) 2014 Flipboard. All rights reserved.
 //
 
-#import <Foundation/Foundation.h>
 #import <UIKit/UIKit.h>
 
 #if !FLEX_AT_LEAST_IOS13_SDK
 @class UIWindowScene;
 #endif
 
-typedef UIViewController *(^FLEXCustomContentViewerFuture)(NSData *data);
-
 @interface FLEXManager : NSObject
 
-+ (instancetype)sharedManager;
+@property (nonatomic, readonly, class) FLEXManager *sharedManager;
 
 @property (nonatomic, readonly) BOOL isHidden;
 
@@ -29,71 +26,13 @@ typedef UIViewController *(^FLEXCustomContentViewerFuture)(NSData *data);
 /// it chooses by default is not the one you wish to display it in.
 - (void)showExplorerFromScene:(UIWindowScene *)scene API_AVAILABLE(ios(13.0));
 
-#pragma mark - Network Debugging
-
-/// If this property is set to YES, FLEX will swizzle NSURLConnection*Delegate and NSURLSession*Delegate methods
-/// on classes that conform to the protocols. This allows you to view network activity history from the main FLEX menu.
-/// Full responses are kept temporarily in a size-limited cache and may be pruned under memory pressure.
-@property (nonatomic, getter=isNetworkDebuggingEnabled) BOOL networkDebuggingEnabled;
-
-/// Defaults to 25 MB if never set. Values set here are persisted across launches of the app.
-/// The response cache uses an NSCache, so it may purge prior to hitting the limit when the app is under memory pressure.
-@property (nonatomic) NSUInteger networkResponseCacheByteLimit;
-
-/// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
-/// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
-/// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
-@property (nonatomic, copy) NSArray<NSString *> *networkRequestHostBlacklist;
-
-
-#pragma mark - Keyboard Shortcuts
-
-/// Simulator keyboard shortcuts are enabled by default.
-/// The shortcuts will not fire when there is an active text field, text view, or other responder accepting key input.
-/// You can disable keyboard shortcuts if you have existing keyboard shortcuts that conflict with FLEX, or if you like doing things the hard way ;)
-/// Keyboard shortcuts are always disabled (and support is compiled out) in non-simulator builds
-@property (nonatomic) BOOL simulatorShortcutsEnabled;
-
-/// Adds an action to run when the specified key & modifier combination is pressed
-/// @param key A single character string matching a key on the keyboard
-/// @param modifiers Modifier keys such as shift, command, or alt/option
-/// @param action The block to run on the main thread when the key & modifier combination is recognized.
-/// @param description Shown the the keyboard shortcut help menu, which is accessed via the '?' key.
-/// @note The action block will be retained for the duration of the application. You may want to use weak references.
-/// @note FLEX registers several default keyboard shortcuts. Use the '?' key to see a list of shortcuts.
-- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description;
-
-#pragma mark - Extensions
+#pragma mark - Misc
 
 /// Default database password is @c nil by default.
 /// Set this to the password you want the databases to open with.
 @property (copy, nonatomic) NSString *defaultSqliteDatabasePassword;
 
-/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
-/// @param entryName The string to be displayed in the cell.
-/// @param objectFutureBlock When you tap on the row, information about the object returned by this block will be displayed.
-/// Passing a block that returns an object allows you to display information about an object whose actual pointer may change at runtime (e.g. +currentUser)
-/// @note This method must be called from the main thread.
-/// The objectFutureBlock will be invoked from the main thread and may return nil.
-/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
-- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock;
-
-/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
-/// @param entryName The string to be displayed in the cell.
-/// @param viewControllerFutureBlock When you tap on the row, view controller returned by this block will be pushed on the navigation controller stack.
-/// @note This method must be called from the main thread.
-/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
-/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
-- (void)registerGlobalEntryWithName:(NSString *)entryName
-          viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock;
+@end
 
-/// Sets custom viewer for specific content type.
-/// @param contentType Mime type like application/json
-/// @param viewControllerFutureBlock Viewer (view controller) creation block
-/// @note This method must be called from the main thread.
-/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
-/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
-- (void)setCustomViewerForContentType:(NSString *)contentType
-            viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock;
 
-@end
+typedef UIViewController *(^FLEXCustomContentViewerFuture)(NSData *data);

+ 60 - 0
Classes/Manager/FLEXManager+Extensibility.h

@@ -0,0 +1,60 @@
+//
+//  FLEXManager+Extensibility.h
+//  FLEX
+//
+//  Created by Tanner on 2/2/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXManager.h"
+
+@interface FLEXManager (Extensibility)
+
+#pragma mark - Globals Screen Entries
+
+/// Adds an entry at the bottom of the list of Global State items.
+/// Call this method before this view controller is displayed.
+/// @param entryName The string to be displayed in the cell.
+/// @param objectFutureBlock When you tap on the row, information about the object returned
+/// by this block will be displayed. Passing a block that returns an object allows you to display
+/// information about an object whose actual pointer may change at runtime (e.g. +currentUser)
+/// @note This method must be called from the main thread.
+/// The objectFutureBlock will be invoked from the main thread and may return nil.
+/// @note The passed block will be copied and retain for the duration of the application,
+/// you may want to use __weak references.
+- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock;
+
+/// Adds an entry at the bottom of the list of Global State items.
+/// Call this method before this view controller is displayed.
+/// @param entryName The string to be displayed in the cell.
+/// @param viewControllerFutureBlock When you tap on the row, view controller returned
+/// by this block will be pushed on the navigation controller stack.
+/// @note This method must be called from the main thread.
+/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
+/// @note The passed block will be copied and retain for the duration of the application,
+/// you may want to use __weak references.
+- (void)registerGlobalEntryWithName:(NSString *)entryName
+          viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock;
+
+#pragma mark - Simulator Shortcuts
+
+/// Simulator keyboard shortcuts are enabled by default.
+/// The shortcuts will not fire when there is an active text field, text view, or other responder
+/// accepting key input. You can disable keyboard shortcuts if you have existing keyboard shortcuts
+/// that conflict with FLEX, or if you like doing things the hard way ;)
+/// Keyboard shortcuts are always disabled (and support is #if'd out) in non-simulator builds
+@property (nonatomic) BOOL simulatorShortcutsEnabled;
+
+/// Adds an action to run when the specified key & modifier combination is pressed
+/// @param key A single character string matching a key on the keyboard
+/// @param modifiers Modifier keys such as shift, command, or alt/option
+/// @param action The block to run on the main thread when the key & modifier combination is recognized.
+/// @param description Shown the the keyboard shortcut help menu, which is accessed via the '?' key.
+/// @note The action block will be retained for the duration of the application. You may want to use weak references.
+/// @note FLEX registers several default keyboard shortcuts. Use the '?' key to see a list of shortcuts.
+- (void)registerSimulatorShortcutWithKey:(NSString *)key
+                               modifiers:(UIKeyModifierFlags)modifiers
+                                  action:(dispatch_block_t)action
+                             description:(NSString *)description;
+
+@end

+ 236 - 0
Classes/Manager/FLEXManager+Extensibility.m

@@ -0,0 +1,236 @@
+//
+//  FLEXManager+Extensibility.m
+//  FLEX
+//
+//  Created by Tanner on 2/2/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXManager+Extensibility.h"
+#import "FLEXManager+Private.h"
+#import "FLEXGlobalsEntry.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXKeyboardShortcutManager.h"
+#import "FLEXExplorerViewController.h"
+#import "FLEXNetworkHistoryTableViewController.h"
+#import "FLEXKeyboardHelpViewController.h"
+#import "FLEXFileBrowserTableViewController.h"
+
+@interface FLEXManager (ExtensibilityPrivate)
+@property (nonatomic, readonly) UIViewController *topViewController;
+@end
+
+@implementation FLEXManager (Extensibility)
+
+#pragma mark - Globals Screen Entries
+
+- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock {
+    NSParameterAssert(entryName);
+    NSParameterAssert(objectFutureBlock);
+    NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
+
+    entryName = entryName.copy;
+    FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
+        return entryName;
+    } viewControllerFuture:^UIViewController *{
+        return [FLEXObjectExplorerFactory explorerViewControllerForObject:objectFutureBlock()];
+    }];
+
+    [self.userGlobalEntries addObject:entry];
+}
+
+- (void)registerGlobalEntryWithName:(NSString *)entryName viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock {
+    NSParameterAssert(entryName);
+    NSParameterAssert(viewControllerFutureBlock);
+    NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
+
+    entryName = entryName.copy;
+    FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
+        return entryName;
+    } viewControllerFuture:^UIViewController *{
+        UIViewController *viewController = viewControllerFutureBlock();
+        NSCAssert(viewController, @"'%@' entry returned nil viewController. viewControllerFutureBlock should never return nil.", entryName);
+        return viewController;
+    }];
+
+    [self.userGlobalEntries addObject:entry];
+}
+
+
+#pragma mark - Simulator Shortcuts
+
+- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description {
+#if TARGET_OS_SIMULATOR
+    [[FLEXKeyboardShortcutManager sharedManager] registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description];
+#endif
+}
+
+- (void)setSimulatorShortcutsEnabled:(BOOL)simulatorShortcutsEnabled {
+#if TARGET_OS_SIMULATOR
+    [[FLEXKeyboardShortcutManager sharedManager] setEnabled:simulatorShortcutsEnabled];
+#endif
+}
+
+- (BOOL)simulatorShortcutsEnabled {
+#if TARGET_OS_SIMULATOR
+    return [[FLEXKeyboardShortcutManager sharedManager] isEnabled];
+#else
+    return NO;
+#endif
+}
+
+- (void)registerDefaultSimulatorShortcuts {
+    [self registerSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
+        [self toggleExplorer];
+    } description:@"Toggle FLEX toolbar"];
+    
+    [self registerSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
+        [self showExplorerIfNeeded];
+        [self.explorerViewController toggleMenuTool];
+    } description:@"Toggle FLEX globals menu"];
+    
+    [self registerSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
+        [self showExplorerIfNeeded];
+        [self.explorerViewController toggleViewsTool];
+    } description:@"Toggle view hierarchy menu"];
+    
+    [self registerSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
+        [self showExplorerIfNeeded];
+        [self.explorerViewController toggleSelectTool];
+    } description:@"Toggle select tool"];
+    
+    [self registerSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
+        [self showExplorerIfNeeded];
+        [self.explorerViewController toggleMoveTool];
+    } description:@"Toggle move tool"];
+    
+    [self registerSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
+        [self toggleTopViewControllerOfClass:[FLEXNetworkHistoryTableViewController class]];
+    } description:@"Toggle network history view"];
+    
+    [self registerSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
+        if (self.isHidden) {
+            [self tryScrollDown];
+        } else {
+            [self.explorerViewController handleDownArrowKeyPressed];
+        }
+    } description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
+    
+    [self registerSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
+        if (self.isHidden) {
+            [self tryScrollUp];
+        } else {
+            [self.explorerViewController handleUpArrowKeyPressed];
+        }
+    } description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
+    
+    [self registerSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
+        if (!self.isHidden) {
+            [self.explorerViewController handleRightArrowKeyPressed];
+        }
+    } description:@"Move selected view right"];
+    
+    [self registerSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
+        if (self.isHidden) {
+            [self tryGoBack];
+        } else {
+            [self.explorerViewController handleLeftArrowKeyPressed];
+        }
+    } description:@"Move selected view left"];
+    
+    [self registerSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
+        [self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
+    } description:@"Toggle (this) help menu"];
+    
+    [self registerSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
+        [[self.topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
+    } description:@"End editing text\n\t\tDismiss top view controller"];
+    
+    [self registerSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
+        [self toggleTopViewControllerOfClass:[FLEXFileBrowserTableViewController class]];
+    } description:@"Toggle file browser menu"];
+}
+
++ (void)load {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self.sharedManager registerDefaultSimulatorShortcuts];
+    });
+}
+
+
+#pragma mark - Private
+
+- (void)tryScrollDown {
+    UIScrollView *firstScrollView = [self firstScrollView];
+    CGPoint contentOffset = firstScrollView.contentOffset;
+    CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
+    CGFloat maxContentOffsetY = firstScrollView.contentSize.height + firstScrollView.contentInset.bottom - firstScrollView.frame.size.height;
+    distance = MIN(maxContentOffsetY - firstScrollView.contentOffset.y, distance);
+    contentOffset.y += distance;
+    [firstScrollView setContentOffset:contentOffset animated:YES];
+}
+
+- (void)tryScrollUp {
+    UIScrollView *firstScrollView = [self firstScrollView];
+    CGPoint contentOffset = firstScrollView.contentOffset;
+    CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
+    CGFloat minContentOffsetY = -firstScrollView.contentInset.top;
+    distance = MIN(firstScrollView.contentOffset.y - minContentOffsetY, distance);
+    contentOffset.y -= distance;
+    [firstScrollView setContentOffset:contentOffset animated:YES];
+}
+
+- (UIScrollView *)firstScrollView {
+    NSMutableArray<UIView *> *views = [UIApplication.sharedApplication.keyWindow.subviews mutableCopy];
+    UIScrollView *scrollView = nil;
+    while (views.count > 0) {
+        UIView *view = views.firstObject;
+        [views removeObjectAtIndex:0];
+        if ([view isKindOfClass:[UIScrollView class]]) {
+            scrollView = (UIScrollView *)view;
+            break;
+        } else {
+            [views addObjectsFromArray:view.subviews];
+        }
+    }
+    return scrollView;
+}
+
+- (void)tryGoBack {
+    UINavigationController *navigationController = nil;
+    UIViewController *topViewController = self.topViewController;
+    if ([topViewController isKindOfClass:[UINavigationController class]]) {
+        navigationController = (UINavigationController *)topViewController;
+    } else {
+        navigationController = topViewController.navigationController;
+    }
+    [navigationController popViewControllerAnimated:YES];
+}
+
+- (UIViewController *)topViewController {
+    UIViewController *topViewController = UIApplication.sharedApplication.keyWindow.rootViewController;
+    while (topViewController.presentedViewController) {
+        topViewController = topViewController.presentedViewController;
+    }
+    return topViewController;
+}
+
+- (void)toggleTopViewControllerOfClass:(Class)class {
+    UINavigationController *topViewController = (id)self.topViewController;
+    if ([topViewController isKindOfClass:[UINavigationController class]] &&
+        [topViewController.topViewController isKindOfClass:[class class]]) {
+        [topViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+    } else {
+        id viewController = [class new];
+        UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
+        [topViewController presentViewController:navigationController animated:YES completion:nil];
+    }
+}
+
+- (void)showExplorerIfNeeded {
+    if (self.isHidden) {
+        [self showExplorer];
+    }
+}
+
+@end

+ 36 - 0
Classes/Manager/FLEXManager+Networking.h

@@ -0,0 +1,36 @@
+//
+//  FLEXManager+Networking.h
+//  FLEX
+//
+//  Created by Tanner on 2/1/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXManager.h"
+
+@interface FLEXManager (Networking)
+
+/// If this property is set to YES, FLEX will swizzle NSURLConnection*Delegate and NSURLSession*Delegate methods
+/// on classes that conform to the protocols. This allows you to view network activity history from the main FLEX menu.
+/// Full responses are kept temporarily in a size-limited cache and may be pruned under memory pressure.
+@property (nonatomic, getter=isNetworkDebuggingEnabled) BOOL networkDebuggingEnabled;
+
+/// Defaults to 25 MB if never set. Values set here are persisted across launches of the app.
+/// The response cache uses an NSCache, so it may purge prior to hitting the limit when the app is under memory pressure.
+@property (nonatomic) NSUInteger networkResponseCacheByteLimit;
+
+/// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
+/// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
+/// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
+@property (nonatomic, copy) NSArray<NSString *> *networkRequestHostBlacklist;
+
+/// Sets custom viewer for specific content type.
+/// @param contentType Mime type like application/json
+/// @param viewControllerFutureBlock Viewer (view controller) creation block
+/// @note This method must be called from the main thread.
+/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
+/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
+- (void)setCustomViewerForContentType:(NSString *)contentType
+            viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock;
+
+@end

+ 48 - 0
Classes/Manager/FLEXManager+Networking.m

@@ -0,0 +1,48 @@
+//
+//  FLEXManager+Networking.m
+//  FLEX
+//
+//  Created by Tanner on 2/1/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXManager+Networking.h"
+#import "FLEXManager+Private.h"
+#import "FLEXNetworkObserver.h"
+#import "FLEXNetworkRecorder.h"
+
+@implementation FLEXManager (Networking)
+
+- (BOOL)isNetworkDebuggingEnabled {
+    return FLEXNetworkObserver.isEnabled;
+}
+
+- (void)setNetworkDebuggingEnabled:(BOOL)networkDebuggingEnabled {
+    FLEXNetworkObserver.enabled = networkDebuggingEnabled;
+}
+
+- (NSUInteger)networkResponseCacheByteLimit {
+    return FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit;
+}
+
+- (void)setNetworkResponseCacheByteLimit:(NSUInteger)networkResponseCacheByteLimit {
+    FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit = networkResponseCacheByteLimit;
+}
+
+- (NSArray<NSString *> *)networkRequestHostBlacklist {
+    return FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
+}
+
+- (void)setNetworkRequestHostBlacklist:(NSArray<NSString *> *)networkRequestHostBlacklist {
+    FLEXNetworkRecorder.defaultRecorder.hostBlacklist = networkRequestHostBlacklist;
+}
+
+- (void)setCustomViewerForContentType:(NSString *)contentType viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock {
+    NSParameterAssert(contentType.length);
+    NSParameterAssert(viewControllerFutureBlock);
+    NSAssert(NSThread.isMainThread, @"This method must be called from the main thread.");
+
+    self.customContentTypeViewers[contentType.lowercaseString] = viewControllerFutureBlock;
+}
+
+@end

+ 7 - 5
Classes/Manager/FLEXManager+Private.h

@@ -8,13 +8,15 @@
 
 #import "FLEXManager.h"
 
-@class FLEXGlobalsEntry;
+@class FLEXGlobalsEntry, FLEXExplorerViewController;
 
-@interface FLEXManager ()
+@interface FLEXManager (Private)
 
-/// An array of FLEXGlobalsEntry objects that have been registered by the user.
-@property (nonatomic, readonly) NSArray<FLEXGlobalsEntry *> *userGlobalEntries;
+//@property (nonatomic) FLEXWindow *explorerWindow;
+@property (nonatomic, readonly) FLEXExplorerViewController *explorerViewController;
 
-@property (nonatomic, readonly) NSDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
+/// An array of FLEXGlobalsEntry objects that have been registered by the user.
+@property (nonatomic, readonly) NSMutableArray<FLEXGlobalsEntry *> *userGlobalEntries;
+@property (nonatomic, readonly) NSMutableDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
 
 @end

+ 24 - 307
Classes/Manager/FLEXManager.m

@@ -10,18 +10,13 @@
 #import "FLEXUtility.h"
 #import "FLEXExplorerViewController.h"
 #import "FLEXWindow.h"
-#import "FLEXGlobalsEntry.h"
-#import "FLEXObjectExplorerFactory.h"
 #import "FLEXObjectExplorerViewController.h"
-#import "FLEXNetworkObserver.h"
-#import "FLEXNetworkRecorder.h"
-#import "FLEXKeyboardShortcutManager.h"
 #import "FLEXFileBrowserTableViewController.h"
-#import "FLEXNetworkHistoryTableViewController.h"
-#import "FLEXKeyboardHelpViewController.h"
 
 @interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
 
+@property (nonatomic, readonly, getter=isHidden) BOOL hidden;
+
 @property (nonatomic) FLEXWindow *explorerWindow;
 @property (nonatomic) FLEXExplorerViewController *explorerViewController;
 
@@ -32,8 +27,7 @@
 
 @implementation FLEXManager
 
-+ (instancetype)sharedManager
-{
++ (instancetype)sharedManager {
     static FLEXManager *sharedManager = nil;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
@@ -42,19 +36,17 @@
     return sharedManager;
 }
 
-- (instancetype)init
-{
+- (instancetype)init {
     self = [super init];
     if (self) {
-        _userGlobalEntries = [NSMutableArray array];
-        _customContentTypeViewers = [NSMutableDictionary dictionary];
+        _userGlobalEntries = [NSMutableArray new];
+        _customContentTypeViewers = [NSMutableDictionary new];
     }
     return self;
 }
 
-- (FLEXWindow *)explorerWindow
-{
-    NSAssert([NSThread isMainThread], @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
+- (FLEXWindow *)explorerWindow {
+    NSAssert(NSThread.isMainThread, @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
     
     if (!_explorerWindow) {
         _explorerWindow = [[FLEXWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
@@ -65,8 +57,7 @@
     return _explorerWindow;
 }
 
-- (FLEXExplorerViewController *)explorerViewController
-{
+- (FLEXExplorerViewController *)explorerViewController {
     if (!_explorerViewController) {
         _explorerViewController = [FLEXExplorerViewController new];
         _explorerViewController.delegate = self;
@@ -75,30 +66,20 @@
     return _explorerViewController;
 }
 
-- (void)showExplorer
-{
-    self.explorerWindow.hidden = NO;
+- (void)showExplorer {
+    UIWindow *flex = self.explorerWindow;
+    flex.hidden = NO;
 #if FLEX_AT_LEAST_IOS13_SDK
     if (@available(iOS 13.0, *)) {
-        // Only look for a new scene if we don't have one, or the one we have
-        // isn't the active scene
-        if (!self.explorerWindow.windowScene ||
-            self.explorerWindow.windowScene.activationState != UISceneActivationStateForegroundActive) {
-            for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
-                // Look for an active UIWindowScene
-                if (scene.activationState == UISceneActivationStateForegroundActive &&
-                    [scene isKindOfClass:[UIWindowScene class]]) {
-                    self.explorerWindow.windowScene = (UIWindowScene *)scene;
-                    break;
-                }
-            }
+        // Only look for a new scene if we don't have one
+        if (!flex.windowScene) {
+            flex.windowScene = FLEXUtility.activeScene;
         }
     }
 #endif
 }
 
-- (void)hideExplorer
-{
+- (void)hideExplorer {
     self.explorerWindow.hidden = YES;
 }
 
@@ -110,8 +91,7 @@
     }
 }
 
-- (void)showExplorerFromScene:(UIWindowScene *)scene
-{
+- (void)showExplorerFromScene:(UIWindowScene *)scene {
     #if FLEX_AT_LEAST_IOS13_SDK
     if (@available(iOS 13.0, *)) {
         self.explorerWindow.windowScene = scene;
@@ -120,292 +100,29 @@
     self.explorerWindow.hidden = NO;
 }
 
-- (BOOL)isHidden
-{
+- (BOOL)isHidden {
     return self.explorerWindow.isHidden;
 }
 
-- (BOOL)isNetworkDebuggingEnabled
-{
-    return [FLEXNetworkObserver isEnabled];
-}
-
-- (void)setNetworkDebuggingEnabled:(BOOL)networkDebuggingEnabled
-{
-    [FLEXNetworkObserver setEnabled:networkDebuggingEnabled];
-}
-
-- (NSUInteger)networkResponseCacheByteLimit
-{
-    return [[FLEXNetworkRecorder defaultRecorder] responseCacheByteLimit];
-}
-
-- (void)setNetworkResponseCacheByteLimit:(NSUInteger)networkResponseCacheByteLimit
-{
-    [[FLEXNetworkRecorder defaultRecorder] setResponseCacheByteLimit:networkResponseCacheByteLimit];
-}
-
-- (void)setNetworkRequestHostBlacklist:(NSArray<NSString *> *)networkRequestHostBlacklist
-{
-    [FLEXNetworkRecorder defaultRecorder].hostBlacklist = networkRequestHostBlacklist;
-}
-
-- (NSArray<NSString *> *)hostBlacklist
-{
-    return [FLEXNetworkRecorder defaultRecorder].hostBlacklist;
-}
 
 #pragma mark - FLEXWindowEventDelegate
 
-- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow
-{
+- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow {
     // Ask the explorer view controller
     return [self.explorerViewController shouldReceiveTouchAtWindowPoint:pointInWindow];
 }
 
-- (BOOL)canBecomeKeyWindow
-{
-    // Only when the explorer view controller wants it because it needs to accept key input & affect the status bar.
-    return [self.explorerViewController wantsWindowToBecomeKey];
+- (BOOL)canBecomeKeyWindow {
+    // Only when the explorer view controller wants it because
+    // it needs to accept key input & affect the status bar.
+    return self.explorerViewController.wantsWindowToBecomeKey;
 }
 
 
 #pragma mark - FLEXExplorerViewControllerDelegate
 
-- (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController
-{
+- (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController {
     [self hideExplorer];
 }
 
-#pragma mark - Simulator Shortcuts
-
-- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description
-{
-# if TARGET_OS_SIMULATOR
-    [[FLEXKeyboardShortcutManager sharedManager] registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description];
-#endif
-}
-
-- (void)setSimulatorShortcutsEnabled:(BOOL)simulatorShortcutsEnabled
-{
-# if TARGET_OS_SIMULATOR
-    [[FLEXKeyboardShortcutManager sharedManager] setEnabled:simulatorShortcutsEnabled];
-#endif
-}
-
-- (BOOL)simulatorShortcutsEnabled
-{
-# if TARGET_OS_SIMULATOR
-    return [[FLEXKeyboardShortcutManager sharedManager] isEnabled];
-#else
-    return NO;
-#endif
-}
-
-- (void)registerDefaultSimulatorShortcuts
-{
-    [self registerSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
-        [self toggleExplorer];
-    } description:@"Toggle FLEX toolbar"];
-    
-    [self registerSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
-        [self showExplorerIfNeeded];
-        [self.explorerViewController toggleMenuTool];
-    } description:@"Toggle FLEX globals menu"];
-    
-    [self registerSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
-        [self showExplorerIfNeeded];
-        [self.explorerViewController toggleViewsTool];
-    } description:@"Toggle view hierarchy menu"];
-    
-    [self registerSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
-        [self showExplorerIfNeeded];
-        [self.explorerViewController toggleSelectTool];
-    } description:@"Toggle select tool"];
-    
-    [self registerSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
-        [self showExplorerIfNeeded];
-        [self.explorerViewController toggleMoveTool];
-    } description:@"Toggle move tool"];
-    
-    [self registerSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
-        [self toggleTopViewControllerOfClass:[FLEXNetworkHistoryTableViewController class]];
-    } description:@"Toggle network history view"];
-    
-    [self registerSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
-        if ([self isHidden]) {
-            [self tryScrollDown];
-        } else {
-            [self.explorerViewController handleDownArrowKeyPressed];
-        }
-    } description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
-    
-    [self registerSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
-        if ([self isHidden]) {
-            [self tryScrollUp];
-        } else {
-            [self.explorerViewController handleUpArrowKeyPressed];
-        }
-    } description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
-    
-    [self registerSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
-        if (![self isHidden]) {
-            [self.explorerViewController handleRightArrowKeyPressed];
-        }
-    } description:@"Move selected view right"];
-    
-    [self registerSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
-        if ([self isHidden]) {
-            [self tryGoBack];
-        } else {
-            [self.explorerViewController handleLeftArrowKeyPressed];
-        }
-    } description:@"Move selected view left"];
-    
-    [self registerSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
-        [self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
-    } description:@"Toggle (this) help menu"];
-    
-    [self registerSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
-        [[[self topViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
-    } description:@"End editing text\n\t\tDismiss top view controller"];
-    
-    [self registerSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
-        [self toggleTopViewControllerOfClass:[FLEXFileBrowserTableViewController class]];
-    } description:@"Toggle file browser menu"];
-}
-
-+ (void)load
-{
-    dispatch_async(dispatch_get_main_queue(), ^{
-        [[[self class] sharedManager] registerDefaultSimulatorShortcuts];
-    });
-}
-
-#pragma mark - Extensions
-
-- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock
-{
-    NSParameterAssert(entryName);
-    NSParameterAssert(objectFutureBlock);
-    NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
-
-    entryName = entryName.copy;
-    FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
-        return entryName;
-    } viewControllerFuture:^UIViewController *{
-        return [FLEXObjectExplorerFactory explorerViewControllerForObject:objectFutureBlock()];
-    }];
-
-    [self.userGlobalEntries addObject:entry];
-}
-
-- (void)registerGlobalEntryWithName:(NSString *)entryName viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock
-{
-    NSParameterAssert(entryName);
-    NSParameterAssert(viewControllerFutureBlock);
-    NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
-
-    entryName = entryName.copy;
-    FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
-        return entryName;
-    } viewControllerFuture:^UIViewController *{
-        UIViewController *viewController = viewControllerFutureBlock();
-        NSCAssert(viewController, @"'%@' entry returned nil viewController. viewControllerFutureBlock should never return nil.", entryName);
-        return viewController;
-    }];
-
-    [self.userGlobalEntries addObject:entry];
-}
-
-- (void)setCustomViewerForContentType:(NSString *)contentType viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock
-{
-    NSParameterAssert(contentType.length);
-    NSParameterAssert(viewControllerFutureBlock);
-    NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
-
-    self.customContentTypeViewers[contentType.lowercaseString] = viewControllerFutureBlock;
-}
-
-- (void)tryScrollDown
-{
-    UIScrollView *firstScrollView = [self firstScrollView];
-    CGPoint contentOffset = firstScrollView.contentOffset;
-    CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
-    CGFloat maxContentOffsetY = firstScrollView.contentSize.height + firstScrollView.contentInset.bottom - firstScrollView.frame.size.height;
-    distance = MIN(maxContentOffsetY - firstScrollView.contentOffset.y, distance);
-    contentOffset.y += distance;
-    [firstScrollView setContentOffset:contentOffset animated:YES];
-}
-
-- (void)tryScrollUp
-{
-    UIScrollView *firstScrollView = [self firstScrollView];
-    CGPoint contentOffset = firstScrollView.contentOffset;
-    CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
-    CGFloat minContentOffsetY = -firstScrollView.contentInset.top;
-    distance = MIN(firstScrollView.contentOffset.y - minContentOffsetY, distance);
-    contentOffset.y -= distance;
-    [firstScrollView setContentOffset:contentOffset animated:YES];
-}
-
-- (UIScrollView *)firstScrollView
-{
-    NSMutableArray<UIView *> *views = [UIApplication.sharedApplication.keyWindow.subviews mutableCopy];
-    UIScrollView *scrollView = nil;
-    while (views.count > 0) {
-        UIView *view = views.firstObject;
-        [views removeObjectAtIndex:0];
-        if ([view isKindOfClass:[UIScrollView class]]) {
-            scrollView = (UIScrollView *)view;
-            break;
-        } else {
-            [views addObjectsFromArray:view.subviews];
-        }
-    }
-    return scrollView;
-}
-
-- (void)tryGoBack
-{
-    UINavigationController *navigationController = nil;
-    UIViewController *topViewController = [self topViewController];
-    if ([topViewController isKindOfClass:[UINavigationController class]]) {
-        navigationController = (UINavigationController *)topViewController;
-    } else {
-        navigationController = topViewController.navigationController;
-    }
-    [navigationController popViewControllerAnimated:YES];
-}
-
-- (UIViewController *)topViewController
-{
-    UIViewController *topViewController = [[UIApplication.sharedApplication keyWindow] rootViewController];
-    while ([topViewController presentedViewController]) {
-        topViewController = [topViewController presentedViewController];
-    }
-    return topViewController;
-}
-
-- (void)toggleTopViewControllerOfClass:(Class)class
-{
-    UIViewController *topViewController = [self topViewController];
-    if ([topViewController isKindOfClass:[UINavigationController class]] &&
-        [[(UINavigationController *)topViewController viewControllers].firstObject isKindOfClass:[class class]])
-    {
-        [[topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
-    } else {
-        id viewController = [class new];
-        UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
-        [topViewController presentViewController:navigationController animated:YES completion:nil];
-    }
-}
-
-- (void)showExplorerIfNeeded
-{
-    if ([self isHidden]) {
-        [self showExplorer];
-    }
-}
-
 @end

+ 1 - 1
Classes/Network/FLEXNetworkRecorder.h

@@ -19,7 +19,7 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
 @interface FLEXNetworkRecorder : NSObject
 
 /// In general, it only makes sense to have one recorder for the entire application.
-+ (instancetype)defaultRecorder;
+@property (nonatomic, readonly, class) FLEXNetworkRecorder *defaultRecorder;
 
 /// Defaults to 25 MB if never set. Values set here are persisted across launches of the app.
 @property (nonatomic) NSUInteger responseCacheByteLimit;

+ 1 - 2
Classes/Network/PonyDebugger/FLEXNetworkObserver.h

@@ -23,7 +23,6 @@ FOUNDATION_EXTERN NSString *const kFLEXNetworkObserverEnabledStateChangedNotific
 /// Swizzling occurs when the observer is enabled for the first time.
 /// This reduces the impact of FLEX if network debugging is not desired.
 /// NOTE: this setting persists between launches of the app.
-+ (void)setEnabled:(BOOL)enabled;
-+ (BOOL)isEnabled;
+@property (nonatomic, class, getter=isEnabled) BOOL enabled;
 
 @end

+ 6 - 7
Classes/Utility/FLEXKeyboardShortcutManager.h

@@ -8,17 +8,16 @@
 
 #import <UIKit/UIKit.h>
 
-#if TARGET_OS_SIMULATOR
-
 @interface FLEXKeyboardShortcutManager : NSObject
 
-+ (instancetype)sharedManager;
+@property (nonatomic, readonly, class) FLEXKeyboardShortcutManager *sharedManager;
 
-- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description;
-- (NSString *)keyboardShortcutsDescription;
+- (void)registerSimulatorShortcutWithKey:(NSString *)key
+                               modifiers:(UIKeyModifierFlags)modifiers
+                                  action:(dispatch_block_t)action
+                             description:(NSString *)description;
 
 @property (nonatomic, getter=isEnabled) BOOL enabled;
+@property (nonatomic, readonly) NSString *keyboardShortcutsDescription;
 
 @end
-
-#endif

+ 10 - 0
Classes/Utility/FLEXUtility.h

@@ -66,8 +66,18 @@ NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
     stringWithFormat:@"%@ %@", @(count), (count == 1 ? singular : plural) \
 ]
 
+#if !FLEX_AT_LEAST_IOS13_SDK
+@class UIWindowScene;
+#endif
+
 @interface FLEXUtility : NSObject
 
+/// The key window of the app, if it is not a \c FLEXWindow.
+/// If it is, then \c FLEXWindow.previousKeyWindow is returned.
+@property (nonatomic, readonly, class) UIWindow *appKeyWindow;
+/// The first active \c UIWindowScene of the app.
+@property (nonatomic, readonly, class) UIWindowScene *activeScene API_AVAILABLE(ios(13.0));
+
 + (UIColor *)consistentRandomColorForObject:(id)object;
 + (NSString *)descriptionForView:(UIView *)view includingFrame:(BOOL)includeFrame;
 + (NSString *)stringForCGRect:(CGRect)rect;

+ 44 - 0
Classes/Utility/FLEXUtility.m

@@ -9,12 +9,56 @@
 #import "FLEXColor.h"
 #import "FLEXUtility.h"
 #import "FLEXResources.h"
+#import "FLEXWindow.h"
 #import <ImageIO/ImageIO.h>
 #import <zlib.h>
 #import <objc/runtime.h>
 
 @implementation FLEXUtility
 
++ (UIWindow *)appKeyWindow {
+    // First, check UIApplication.keyWindow
+    FLEXWindow *window = (id)UIApplication.sharedApplication.keyWindow;
+    if (window) {
+        if ([window isKindOfClass:[FLEXWindow class]]) {
+            return window.previousKeyWindow;
+        }
+        
+        return window;
+    }
+    
+    // As of iOS 13, UIApplication.keyWindow does not return nil,
+    // so this is more of a safeguard against it returning nil in the future.
+    //
+    // Also, these are obviously not all FLEXWindows; FLEXWindow is used
+    // so we can call window.previousKeyWindow without an ugly cast
+    for (FLEXWindow *window in UIApplication.sharedApplication.windows) {
+        if (window.isKeyWindow) {
+            if ([window isKindOfClass:[FLEXWindow class]]) {
+                return window.previousKeyWindow;
+            }
+            
+            return window;
+        }
+    }
+    
+    return nil;
+}
+
+#if FLEX_AT_LEAST_IOS13_SDK
++ (UIWindowScene *)activeScene {
+    for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
+        // Look for an active UIWindowScene
+        if (scene.activationState == UISceneActivationStateForegroundActive &&
+            [scene isKindOfClass:[UIWindowScene class]]) {
+            return (UIWindowScene *)scene;
+        }
+    }
+    
+    return nil;
+}
+#endif
+
 + (UIColor *)consistentRandomColorForObject:(id)object
 {
     CGFloat hue = (((NSUInteger)object >> 4) % 256) / 255.0;

+ 16 - 0
FLEX.xcodeproj/project.pbxproj

@@ -172,6 +172,10 @@
 		C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */; };
 		C3511B9122D7C99E0057BAB7 /* FLEXGlobalsSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3511B8F22D7C99E0057BAB7 /* FLEXGlobalsSection.h */; };
 		C3511B9222D7C99E0057BAB7 /* FLEXGlobalsSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3511B9022D7C99E0057BAB7 /* FLEXGlobalsSection.m */; };
+		C3531B9D23E69BB200A184AD /* FLEXManager+Networking.h in Headers */ = {isa = PBXBuildFile; fileRef = C3531B9B23E69BB200A184AD /* FLEXManager+Networking.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		C3531B9E23E69BB200A184AD /* FLEXManager+Networking.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531B9C23E69BB200A184AD /* FLEXManager+Networking.m */; };
+		C3531BA123E796BD00A184AD /* FLEXManager+Extensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = C3531B9F23E796BD00A184AD /* FLEXManager+Extensibility.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		C3531BA223E796BD00A184AD /* FLEXManager+Extensibility.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA023E796BD00A184AD /* FLEXManager+Extensibility.m */; };
 		C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; };
 		C362AE8223C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m in Sources */ = {isa = PBXBuildFile; fileRef = C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */; };
 		C36B096523E0D4A1008F5D47 /* UIMenu+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C36B096323E0D4A1008F5D47 /* UIMenu+FLEX.h */; };
@@ -489,6 +493,10 @@
 		C34EE30A21CB249E00BD3A7C /* ActivityStreamAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStreamAPI.h; sourceTree = "<group>"; };
 		C3511B8F22D7C99E0057BAB7 /* FLEXGlobalsSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXGlobalsSection.h; sourceTree = "<group>"; };
 		C3511B9022D7C99E0057BAB7 /* FLEXGlobalsSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXGlobalsSection.m; sourceTree = "<group>"; };
+		C3531B9B23E69BB200A184AD /* FLEXManager+Networking.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXManager+Networking.h"; sourceTree = "<group>"; };
+		C3531B9C23E69BB200A184AD /* FLEXManager+Networking.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXManager+Networking.m"; sourceTree = "<group>"; };
+		C3531B9F23E796BD00A184AD /* FLEXManager+Extensibility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXManager+Extensibility.h"; sourceTree = "<group>"; };
+		C3531BA023E796BD00A184AD /* FLEXManager+Extensibility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXManager+Extensibility.m"; sourceTree = "<group>"; };
 		C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMapTable+FLEX_Subscripting.h"; sourceTree = "<group>"; };
 		C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMapTable+FLEX_Subscripting.m"; sourceTree = "<group>"; };
 		C36B096323E0D4A1008F5D47 /* UIMenu+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIMenu+FLEX.h"; sourceTree = "<group>"; };
@@ -940,6 +948,10 @@
 			children = (
 				94A515161C4CA1D70063292F /* FLEXManager+Private.h */,
 				94A515151C4CA1D70063292F /* FLEXManager.m */,
+				C3531B9F23E796BD00A184AD /* FLEXManager+Extensibility.h */,
+				C3531BA023E796BD00A184AD /* FLEXManager+Extensibility.m */,
+				C3531B9B23E69BB200A184AD /* FLEXManager+Networking.h */,
+				C3531B9C23E69BB200A184AD /* FLEXManager+Networking.m */,
 			);
 			path = Manager;
 			sourceTree = "<group>";
@@ -1259,6 +1271,7 @@
 				C3E5DA02231700EE00E655DB /* FLEXObjectInfoSection.h in Headers */,
 				C34D4EB023A2ABD900C1F903 /* FLEXLayerShortcuts.h in Headers */,
 				C3F646F623A04A7500D4A011 /* FLEXShortcut.h in Headers */,
+				C3531B9D23E69BB200A184AD /* FLEXManager+Networking.h in Headers */,
 				C3878DBB23A749960038FDBE /* FLEXVariableEditorViewController.h in Headers */,
 				3A4C95361B5B21410088C3F2 /* FLEXNetworkHistoryTableViewController.h in Headers */,
 				3A4C94DD1B5B21410088C3F2 /* FLEXHeapEnumerator.h in Headers */,
@@ -1281,6 +1294,7 @@
 				3A4C94E31B5B21410088C3F2 /* FLEXRuntimeUtility.h in Headers */,
 				C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */,
 				C31D93E823E38E97005517BF /* FLEXBlockDescription.h in Headers */,
+				C3531BA123E796BD00A184AD /* FLEXManager+Extensibility.h in Headers */,
 				C36FBFCF230F3B98008D95D5 /* FLEXProtocol.h in Headers */,
 				94A515271C4CA2080063292F /* FLEXToolbarItem.h in Headers */,
 				3A4C95341B5B21410088C3F2 /* FLEXSystemLogTableViewController.h in Headers */,
@@ -1581,6 +1595,7 @@
 				3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
 				C383C3C623B6BB81007A321B /* FLEXCodeFontCell.m in Sources */,
 				3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
+				C3531BA223E796BD00A184AD /* FLEXManager+Extensibility.m in Sources */,
 				71E1C2172307FBB800F5032A /* FLEXKeychain.m in Sources */,
 				C3A9423923C51B8D006871A3 /* FHSRangeSlider.m in Sources */,
 				C3F646F323A045DB00D4A011 /* FLEXClassShortcuts.m in Sources */,
@@ -1598,6 +1613,7 @@
 				C3A9423523C3F98E006871A3 /* FHSViewController.m in Sources */,
 				C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */,
 				3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
+				C3531B9E23E69BB200A184AD /* FLEXManager+Networking.m in Sources */,
 				C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
 				C36B096623E0D4A1008F5D47 /* UIMenu+FLEX.m in Sources */,
 				94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,