123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- // The MIT License
- //
- // Copyright (c) 2016 Dariusz Bukowski
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- #import "DBDebugToolkit.h"
- #import "DBShakeTrigger.h"
- #import "DBMenuTableViewController.h"
- #import "NSBundle+DBDebugToolkit.h"
- #import "DBPerformanceWidgetView.h"
- #import "DBPerformanceToolkit.h"
- #import "DBPerformanceTableViewController.h"
- #import "DBConsoleOutputCaptor.h"
- #import "DBNetworkToolkit.h"
- #import "DBUserInterfaceToolkit.h"
- #import "DBLocationToolkit.h"
- #import "DBKeychainToolkit.h"
- #import "DBUserDefaultsToolkit.h"
- #import "DBCoreDataToolkit.h"
- #import "DBCrashReportsToolkit.h"
- #import "DBTopLevelViewsWrapper.h"
- #import "UIApplication+DBDebugToolkit.h"
- static NSString *const DBDebugToolkitObserverPresentationControllerPropertyKeyPath = @"containerView";
- @interface DBDebugToolkit () <DBDebugToolkitTriggerDelegate, DBMenuTableViewControllerDelegate, DBPerformanceWidgetViewDelegate>
- @property (nonatomic, copy) NSArray <id <DBDebugToolkitTrigger>> *triggers;
- @property (nonatomic, strong) DBMenuTableViewController *menuViewController;
- @property (nonatomic, assign) BOOL showsMenu;
- @property (nonatomic, strong) DBPerformanceToolkit *performanceToolkit;
- @property (nonatomic, strong) DBConsoleOutputCaptor *consoleOutputCaptor;
- @property (nonatomic, strong) DBNetworkToolkit *networkToolkit;
- @property (nonatomic, strong) DBUserInterfaceToolkit *userInterfaceToolkit;
- @property (nonatomic, strong) DBLocationToolkit *locationToolkit;
- @property (nonatomic, strong) DBCoreDataToolkit *coreDataToolkit;
- @property (nonatomic, strong) DBCrashReportsToolkit *crashReportsToolkit;
- @property (nonatomic, strong) NSMutableArray <DBCustomAction *> *customActions;
- @property (nonatomic, strong) NSMutableDictionary <NSString *, DBCustomVariable *> *customVariables;
- @property (nonatomic, strong) DBTopLevelViewsWrapper *topLevelViewsWrapper;
- @end
- @implementation DBDebugToolkit
- #pragma mark - Setup
- + (void)setup {
- NSArray <id <DBDebugToolkitTrigger>> *defaultTriggers = [self defaultTriggers];
- [self setupWithTriggers:defaultTriggers];
- }
- + (void)setupWithTriggers:(NSArray<id<DBDebugToolkitTrigger>> *)triggers {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- toolkit.triggers = triggers;
- }
- + (NSArray <id <DBDebugToolkitTrigger>> *)defaultTriggers {
- return [NSArray arrayWithObject:[DBShakeTrigger trigger]];
- }
- + (instancetype)sharedInstance {
- static DBDebugToolkit *sharedInstance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- sharedInstance = [[DBDebugToolkit alloc] init];
- [sharedInstance registerForNotifications];
- [sharedInstance setupTopLevelViewsWrapper];
- [sharedInstance setupPerformanceToolkit];
- [sharedInstance setupConsoleOutputCaptor];
- [sharedInstance setupNetworkToolkit];
- [sharedInstance setupUserInterfaceToolkit];
- [sharedInstance setupLocationToolkit];
- [sharedInstance setupCoreDataToolkit];
- [sharedInstance setupCustomActions];
- [sharedInstance setupCustomVariables];
- [sharedInstance setupCrashReportsToolkit];
- });
- return sharedInstance;
- }
- - (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- #pragma mark - Setting triggers
- - (void)setTriggers:(NSArray<id<DBDebugToolkitTrigger>> *)triggers {
- _triggers = [triggers copy];
- UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
- [self addTriggersToWindow:keyWindow];
- for (id <DBDebugToolkitTrigger> trigger in triggers) {
- trigger.delegate = self;
- }
- }
- - (void)addTriggersToWindow:(UIWindow *)window {
- for (id <DBDebugToolkitTrigger> trigger in self.triggers) {
- [trigger addToWindow:window];
- }
- }
- - (void)removeTriggersFromWindow:(UIWindow *)window {
- for (id <DBDebugToolkitTrigger> trigger in self.triggers) {
- [trigger removeFromWindow:window];
- }
- }
- #pragma mark - Window notifications
- - (void)registerForNotifications {
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(newKeyWindowNotification:)
- name:UIWindowDidBecomeKeyNotification
- object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(windowDidResignKeyNotification:)
- name:UIWindowDidResignKeyNotification
- object:nil];
- }
- - (void)newKeyWindowNotification:(NSNotification *)notification {
- UIWindow *newKeyWindow = notification.object;
- [self addTriggersToWindow:newKeyWindow];
- [self addTopLevelViewsWrapperToWindow:newKeyWindow];
- }
- - (void)windowDidResignKeyNotification:(NSNotification *)notification {
- UIWindow *windowResigningKey = notification.object;
- [self removeTriggersFromWindow:windowResigningKey];
- }
- #pragma mark - Performance toolkit
- - (void)setupPerformanceToolkit {
- self.performanceToolkit = [[DBPerformanceToolkit alloc] initWithWidgetDelegate:self];
- [self.topLevelViewsWrapper addTopLevelView:self.performanceToolkit.widget];
- }
- #pragma mark - Console output captor
- - (void)setupConsoleOutputCaptor {
- self.consoleOutputCaptor = [DBConsoleOutputCaptor sharedInstance];
- self.consoleOutputCaptor.enabled = YES;
- }
- + (void)setCapturingConsoleOutputEnabled:(BOOL)enabled {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- toolkit.consoleOutputCaptor.enabled = enabled;
- }
- #pragma mark - Network toolkit
- - (void)setupNetworkToolkit {
- self.networkToolkit = [DBNetworkToolkit sharedInstance];
- self.networkToolkit.loggingEnabled = YES;
- }
- + (void)setNetworkRequestsLoggingEnabled:(BOOL)enabled {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- toolkit.networkToolkit.loggingEnabled = enabled;
- }
- #pragma mark - User interface toolkit
- - (void)setupUserInterfaceToolkit {
- self.userInterfaceToolkit = [DBUserInterfaceToolkit sharedInstance];
- self.userInterfaceToolkit.colorizedViewBordersEnabled = NO;
- self.userInterfaceToolkit.slowAnimationsEnabled = NO;
- self.userInterfaceToolkit.showingTouchesEnabled = NO;
- [self.userInterfaceToolkit setupDebuggingInformationOverlay];
- [self.userInterfaceToolkit setupGridOverlay];
- [self.topLevelViewsWrapper addTopLevelView:self.userInterfaceToolkit.gridOverlay];
- }
- #pragma mark - Location toolkit
- - (void)setupLocationToolkit {
- self.locationToolkit = [DBLocationToolkit sharedInstance];
- }
- #pragma mark - Core Data toolkit
- - (void)setupCoreDataToolkit {
- self.coreDataToolkit = [DBCoreDataToolkit sharedInstance];
- }
- #pragma mark - Custom actions
- - (void)setupCustomActions {
- self.customActions = [NSMutableArray array];
- }
- + (void)addCustomAction:(DBCustomAction *)customAction {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- [toolkit.customActions addObject:customAction];
- }
- + (void)addCustomActions:(NSArray <DBCustomAction *> *)customActions {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- [toolkit.customActions addObjectsFromArray:customActions];
- }
- + (void)removeCustomAction:(DBCustomAction *)customAction {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- [toolkit.customActions removeObject:customAction];
- }
- + (void)removeCustomActions:(NSArray <DBCustomAction *> *)customActions {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- [toolkit.customActions removeObjectsInArray:customActions];
- }
- #pragma mark - Custom variables
- - (void)setupCustomVariables {
- self.customVariables = [NSMutableDictionary dictionary];
- }
- + (void)addCustomVariable:(DBCustomVariable *)customVariable {
- [self removeCustomVariableWithName:customVariable.name];
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- toolkit.customVariables[customVariable.name] = customVariable;
- }
- + (void)addCustomVariables:(NSArray <DBCustomVariable *> *)customVariables {
- for (DBCustomVariable *customVariable in customVariables) {
- [self addCustomVariable:customVariable];
- }
- }
- + (void)removeCustomVariableWithName:(NSString *)variableName {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- DBCustomVariable *customVariable = toolkit.customVariables[variableName];
- [customVariable removeTarget:nil action:nil];
- toolkit.customVariables[variableName] = nil;
- }
- + (void)removeCustomVariablesWithNames:(NSArray <NSString *> *)variableNames {
- for (NSString *variableName in variableNames) {
- [self removeCustomVariableWithName:variableName];
- }
- }
- + (DBCustomVariable *)customVariableWithName:(NSString *)variableName {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- return toolkit.customVariables[variableName];
- }
- #pragma mark - Crash reports toolkit
- - (void)setupCrashReportsToolkit {
- self.crashReportsToolkit = [DBCrashReportsToolkit sharedInstance];
- self.crashReportsToolkit.consoleOutputCaptor = self.consoleOutputCaptor;
- self.crashReportsToolkit.buildInfoProvider = [DBBuildInfoProvider new];
- self.crashReportsToolkit.deviceInfoProvider = [DBDeviceInfoProvider new];
- }
- + (void)setupCrashReporting {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- [toolkit.crashReportsToolkit setupCrashReporting];
- }
- #pragma mark - Resources
- + (void)clearKeychain {
- DBKeychainToolkit *keychainToolkit = [DBKeychainToolkit new];
- [keychainToolkit handleClearAction];
- }
- + (void)clearUserDefaults {
- DBUserDefaultsToolkit *userDefaultsToolkit = [DBUserDefaultsToolkit new];
- [userDefaultsToolkit handleClearAction];
- }
- + (void)clearDocuments {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
- NSArray <NSString *> *documentsContents = [fileManager contentsOfDirectoryAtPath:documentsPath error:nil];
- for (NSString *file in documentsContents) {
- NSString *filePath = [documentsPath stringByAppendingPathComponent:file];
- [fileManager removeItemAtPath:filePath error:nil];
- }
- }
- + (void)clearCookies {
- NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
- for (NSHTTPCookie *cookie in cookieStorage.cookies) {
- [cookieStorage deleteCookie:cookie];
- }
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
- #pragma mark - Shortcut items
- + (void)addClearDataShortcutItem {
- if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
- // Shortcut items are not supported on the running iOS version.
- return;
- }
- NSArray <UIApplicationShortcutItem *> *shortcutItems = UIApplication.sharedApplication.shortcutItems;
- for (UIApplicationShortcutItem *item in shortcutItems) {
- if ([item.type isEqualToString:DBClearDataShortcutItemType]) {
- // We already added `Clear data` shortcut item.
- return;
- }
- }
- UIApplicationShortcutIcon *shortcutIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeCompose];
- UIApplicationShortcutItem *clearDataShortcutItem = [[UIApplicationShortcutItem alloc] initWithType:DBClearDataShortcutItemType
- localizedTitle:@"Clear data"
- localizedSubtitle:@"DBDebugToolkit"
- icon:shortcutIcon
- userInfo:nil];
- NSMutableArray *newShortcutItems = shortcutItems == nil ? [NSMutableArray array] : [NSMutableArray arrayWithArray:shortcutItems];
- [newShortcutItems insertObject:clearDataShortcutItem atIndex:0];
- UIApplication.sharedApplication.shortcutItems = [newShortcutItems copy];
- }
- + (void)handleClearDataShortcutItemAction {
- [self clearUserDefaults];
- [self clearKeychain];
- [self clearDocuments];
- [self clearCookies];
- }
- #pragma mark - Top level views
- - (void)setupTopLevelViewsWrapper {
- self.topLevelViewsWrapper = [DBTopLevelViewsWrapper new];
- UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
- [self addTopLevelViewsWrapperToWindow:keyWindow];
- }
- - (void)addTopLevelViewsWrapperToWindow:(UIWindow *)window {
- [self.topLevelViewsWrapper.superview removeObserver:self forKeyPath:@"layer.sublayers"];
- [window addSubview:self.topLevelViewsWrapper];
- // We observe the "layer.sublayers" property of the window to keep the widget on top.
- [window addObserver:self
- forKeyPath:@"layer.sublayers"
- options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
- context:nil];
- }
- #pragma mark - Convenience methods
- + (void)showMenu {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- if (!toolkit.showsMenu) {
- [toolkit showMenu];
- }
- }
- + (void)showPerformanceWidget {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- DBPerformanceToolkit *performanceToolkit = toolkit.performanceToolkit;
- performanceToolkit.isWidgetShown = YES;
- }
- + (void)showDebuggingInformationOverlay {
- DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
- DBUserInterfaceToolkit *userInterfaceToolkit = toolkit.userInterfaceToolkit;
- if (userInterfaceToolkit.isDebuggingInformationOverlayAvailable) {
- [userInterfaceToolkit showDebuggingInformationOverlay];
- }
- }
- #pragma mark - Showing menu
- - (void)showMenu {
- self.showsMenu = YES;
- UIViewController *presentingViewController = [self topmostViewController];
- UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.menuViewController];
- navigationController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
- navigationController.modalPresentationStyle = UIModalPresentationOverFullScreen;
- navigationController.modalPresentationCapturesStatusBarAppearance = YES;
- [presentingViewController presentViewController:navigationController animated:YES completion:^{
- // We need this to properly handle a case of menu being dismissed because of dismissing of the view controller that presents it.
- [navigationController.presentationController addObserver:self
- forKeyPath:DBDebugToolkitObserverPresentationControllerPropertyKeyPath
- options:0
- context:nil];
- }];
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
- if ([object isKindOfClass:[UIPresentationController class]]) {
- UIPresentationController *presentationController = (UIPresentationController *)object;
- if (presentationController.containerView == nil) {
- // The menu was dismissed.
- self.showsMenu = NO;
- [presentationController removeObserver:self forKeyPath:DBDebugToolkitObserverPresentationControllerPropertyKeyPath];
- }
- } else if ([object isKindOfClass:[UIWindow class]]) {
- [self.topLevelViewsWrapper.superview bringSubviewToFront:self.topLevelViewsWrapper];
- }
- }
- - (DBMenuTableViewController *)menuViewController {
- if (!_menuViewController) {
- NSBundle *bundle = [NSBundle debugToolkitBundle];
- UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBMenuTableViewController" bundle:bundle];
- _menuViewController = [storyboard instantiateInitialViewController];
- _menuViewController.performanceToolkit = self.performanceToolkit;
- _menuViewController.consoleOutputCaptor = self.consoleOutputCaptor;
- _menuViewController.networkToolkit = self.networkToolkit;
- _menuViewController.userInterfaceToolkit = self.userInterfaceToolkit;
- _menuViewController.locationToolkit = self.locationToolkit;
- _menuViewController.coreDataToolkit = self.coreDataToolkit;
- _menuViewController.crashReportsToolkit = self.crashReportsToolkit;
- _menuViewController.buildInfoProvider = [DBBuildInfoProvider new];
- _menuViewController.deviceInfoProvider = [DBDeviceInfoProvider new];
- _menuViewController.delegate = self;
- }
- _menuViewController.customVariables = self.customVariables.allValues;
- _menuViewController.customActions = self.customActions;
- return _menuViewController;
- }
- - (UIViewController *)topmostViewController {
- UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
- return [self topmostViewControllerWithRootViewController:rootViewController];
- }
- - (UIViewController *)topmostViewControllerWithRootViewController:(UIViewController *)rootViewController {
- if ([rootViewController isKindOfClass:[UITabBarController class]]) {
- UITabBarController *tabBarController = (UITabBarController *)rootViewController;
- return [self topmostViewControllerWithRootViewController:tabBarController.selectedViewController];
- } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
- UINavigationController *navigationController = (UINavigationController *)rootViewController;
- if (navigationController.visibleViewController == nil) {
- return navigationController;
- }
- return [self topmostViewControllerWithRootViewController:navigationController.visibleViewController];
- } else if (rootViewController.presentedViewController) {
- UIViewController* presentedViewController = rootViewController.presentedViewController;
- return [self topmostViewControllerWithRootViewController:presentedViewController];
- }
- return rootViewController;
- }
- #pragma mark - DBDebugToolkitTriggerDelegate
- - (void)debugToolkitTriggered:(id<DBDebugToolkitTrigger>)trigger {
- if (!self.showsMenu) {
- [self showMenu];
- }
- }
- #pragma mark - DBMenuTableViewControllerDelegate
- - (void)menuTableViewControllerDidTapClose:(DBMenuTableViewController *)menuTableViewController {
- UIViewController *presentingViewController = self.menuViewController.navigationController.presentingViewController;
- [presentingViewController dismissViewControllerAnimated:YES completion:^{
- self.showsMenu = NO;
- }];
- }
- #pragma mark - DBPerformanceWidgetViewDelegate
- - (void)performanceWidgetView:(DBPerformanceWidgetView *)performanceWidgetView didTapOnSection:(DBPerformanceSection)section {
- BOOL shouldAnimateShowingPerformance = YES;
- if (!self.showsMenu) {
- [self showMenu];
- shouldAnimateShowingPerformance = NO;
- }
- UINavigationController *navigationController = self.menuViewController.navigationController;
- if (navigationController.viewControllers.count > 1 && [navigationController.viewControllers[1] isKindOfClass:[DBPerformanceTableViewController class]]) {
- // Only update the presented DBPerformanceTableViewController instance.
- DBPerformanceTableViewController *performanceTableViewController = (DBPerformanceTableViewController *)navigationController.viewControllers[1];
- performanceTableViewController.selectedSection = section;
- } else {
- // Update navigation controller's view controllers.
- [self.menuViewController openPerformanceMenuWithSection:section
- animated:shouldAnimateShowingPerformance];
- }
- }
- @end
|