// // FLEXObjectExplorerFactory.m // Flipboard // // Created by Ryan Olson on 5/15/14. // Copyright (c) 2020 FLEX Team. All rights reserved. // #import "FLEXObjectExplorerFactory.h" #import "FLEXGlobalsViewController.h" #import "FLEXClassShortcuts.h" #import "FLEXViewShortcuts.h" #import "FLEXViewControllerShortcuts.h" #import "FLEXUIAppShortcuts.h" #import "FLEXImageShortcuts.h" #import "FLEXLayerShortcuts.h" #import "FLEXColorPreviewSection.h" #import "FLEXDefaultsContentSection.h" #import "FLEXBundleShortcuts.h" #import "FLEXBlockShortcuts.h" #import "FLEXUtility.h" @implementation FLEXObjectExplorerFactory static NSMutableDictionary, Class> *classesToRegisteredSections = nil; + (void)initialize { if (self == [FLEXObjectExplorerFactory class]) { // DO NOT USE STRING KEYS HERE // We NEED to use the class as a key, because we CANNOT // differentiate a class's name from the metaclass's name. // These mappings are per-class-object, not per-class-name. // // For example, if we used class names, this would result in // the object explorer trying to render a color preview for // the UIColor class object, which is not a color itself. #define ClassKey(name) (id)[name class] #define ClassKeyByName(str) (id)NSClassFromString(@ #str) #define MetaclassKey(meta) (id)object_getClass([meta class]) classesToRegisteredSections = [NSMutableDictionary dictionaryWithDictionary:@{ MetaclassKey(NSObject) : [FLEXClassShortcuts class], ClassKey(NSArray) : [FLEXCollectionContentSection class], ClassKey(NSSet) : [FLEXCollectionContentSection class], ClassKey(NSDictionary) : [FLEXCollectionContentSection class], ClassKey(NSOrderedSet) : [FLEXCollectionContentSection class], ClassKey(NSUserDefaults) : [FLEXDefaultsContentSection class], ClassKey(UIViewController) : [FLEXViewControllerShortcuts class], ClassKey(UIApplication) : [FLEXUIAppShortcuts class], ClassKey(UIView) : [FLEXViewShortcuts class], ClassKey(UIImage) : [FLEXImageShortcuts class], ClassKey(CALayer) : [FLEXLayerShortcuts class], ClassKey(UIColor) : [FLEXColorPreviewSection class], ClassKey(NSBundle) : [FLEXBundleShortcuts class], ClassKeyByName(NSBlock) : [FLEXBlockShortcuts class], }]; #undef ClassKey #undef ClassKeyByName #undef MetaclassKey } } + (FLEXObjectExplorerViewController *)explorerViewControllerForObject:(id)object { // Can't explore nil if (!object) { return nil; } // If we're given an object, this will look up it's class hierarchy // until it finds a registration. This will work for KVC classes, // since they are children of the original class, and not siblings. // If we are given an object, object_getClass will return a metaclass, // and the same thing will happen. FLEXClassShortcuts is the default // shortcut section for NSObject. // // TODO: rename it to FLEXNSObjectShortcuts or something? Class sectionClass = nil; Class cls = object_getClass(object); do { sectionClass = classesToRegisteredSections[(id)cls]; } while (!sectionClass && (cls = [cls superclass])); if (!sectionClass) { sectionClass = [FLEXShortcutsSection class]; } return [FLEXObjectExplorerViewController exploringObject:object customSection:[sectionClass forObject:object] ]; } + (void)registerExplorerSection:(Class)explorerClass forClass:(Class)objectClass { classesToRegisteredSections[(id)objectClass] = explorerClass; } #pragma mark - FLEXGlobalsEntry + (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row { switch (row) { case FLEXGlobalsRowAppDelegate: return @"๐ŸŽŸ App Delegate"; case FLEXGlobalsRowKeyWindow: return @"๐Ÿ”‘ Key Window"; case FLEXGlobalsRowRootViewController: return @"๐ŸŒด Root View Controller"; case FLEXGlobalsRowProcessInfo: return @"๐Ÿšฆ NSProcessInfo.processInfo"; case FLEXGlobalsRowUserDefaults: return @"๐Ÿ’พ Preferences"; case FLEXGlobalsRowMainBundle: return @"๐Ÿ“ฆ NSBundle.mainBundle"; case FLEXGlobalsRowApplication: return @"๐Ÿš€ UIApplication.sharedApplication"; case FLEXGlobalsRowMainScreen: return @"๐Ÿ’ป UIScreen.mainScreen"; case FLEXGlobalsRowCurrentDevice: return @"๐Ÿ“ฑ UIDevice.currentDevice"; case FLEXGlobalsRowPasteboard: return @"๐Ÿ“‹ UIPasteboard.generalPasteboard"; case FLEXGlobalsRowURLSession: return @"๐Ÿ“ก NSURLSession.sharedSession"; case FLEXGlobalsRowURLCache: return @"โณ NSURLCache.sharedURLCache"; case FLEXGlobalsRowNotificationCenter: return @"๐Ÿ”” NSNotificationCenter.defaultCenter"; case FLEXGlobalsRowMenuController: return @"๐Ÿ“Ž UIMenuController.sharedMenuController"; case FLEXGlobalsRowFileManager: return @"๐Ÿ—„ NSFileManager.defaultManager"; case FLEXGlobalsRowTimeZone: return @"๐ŸŒŽ NSTimeZone.systemTimeZone"; case FLEXGlobalsRowLocale: return @"๐Ÿ—ฃ NSLocale.currentLocale"; case FLEXGlobalsRowCalendar: return @"๐Ÿ“… NSCalendar.currentCalendar"; case FLEXGlobalsRowMainRunLoop: return @"๐Ÿƒ๐Ÿปโ€โ™‚๏ธ NSRunLoop.mainRunLoop"; case FLEXGlobalsRowMainThread: return @"๐Ÿงต NSThread.mainThread"; case FLEXGlobalsRowOperationQueue: return @"๐Ÿ“š NSOperationQueue.mainQueue"; default: return nil; } } + (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row { switch (row) { case FLEXGlobalsRowAppDelegate: { id appDelegate = UIApplication.sharedApplication.delegate; return [self explorerViewControllerForObject:appDelegate]; } case FLEXGlobalsRowProcessInfo: return [self explorerViewControllerForObject:NSProcessInfo.processInfo]; case FLEXGlobalsRowUserDefaults: return [self explorerViewControllerForObject:NSUserDefaults.standardUserDefaults]; case FLEXGlobalsRowMainBundle: return [self explorerViewControllerForObject:NSBundle.mainBundle]; case FLEXGlobalsRowApplication: return [self explorerViewControllerForObject:UIApplication.sharedApplication]; case FLEXGlobalsRowMainScreen: return [self explorerViewControllerForObject:UIScreen.mainScreen]; case FLEXGlobalsRowCurrentDevice: return [self explorerViewControllerForObject:UIDevice.currentDevice]; case FLEXGlobalsRowPasteboard: #if !TARGET_OS_TV return [self explorerViewControllerForObject:UIPasteboard.generalPasteboard]; #else return nil; //FIXME: Probably not a safe fix #endif case FLEXGlobalsRowURLSession: return [self explorerViewControllerForObject:NSURLSession.sharedSession]; case FLEXGlobalsRowURLCache: return [self explorerViewControllerForObject:NSURLCache.sharedURLCache]; case FLEXGlobalsRowNotificationCenter: return [self explorerViewControllerForObject:NSNotificationCenter.defaultCenter]; case FLEXGlobalsRowMenuController: #if !TARGET_OS_TV return [self explorerViewControllerForObject:UIMenuController.sharedMenuController]; #else return nil; //FIXME: Probably not a safe fix #endif case FLEXGlobalsRowFileManager: return [self explorerViewControllerForObject:NSFileManager.defaultManager]; case FLEXGlobalsRowTimeZone: return [self explorerViewControllerForObject:NSTimeZone.systemTimeZone]; case FLEXGlobalsRowLocale: return [self explorerViewControllerForObject:NSLocale.currentLocale]; case FLEXGlobalsRowCalendar: return [self explorerViewControllerForObject:NSCalendar.currentCalendar]; case FLEXGlobalsRowMainRunLoop: return [self explorerViewControllerForObject:NSRunLoop.mainRunLoop]; case FLEXGlobalsRowMainThread: return [self explorerViewControllerForObject:NSThread.mainThread]; case FLEXGlobalsRowOperationQueue: return [self explorerViewControllerForObject:NSOperationQueue.mainQueue]; case FLEXGlobalsRowKeyWindow: return [FLEXObjectExplorerFactory explorerViewControllerForObject:FLEXUtility.appKeyWindow ]; case FLEXGlobalsRowRootViewController: { id delegate = UIApplication.sharedApplication.delegate; if ([delegate respondsToSelector:@selector(window)]) { return [self explorerViewControllerForObject:delegate.window.rootViewController]; } return nil; } default: return nil; } } + (FLEXGlobalsEntryRowAction)globalsEntryRowAction:(FLEXGlobalsRow)row { switch (row) { case FLEXGlobalsRowRootViewController: { // Check if the app delegate responds to -window. If not, present an alert return ^(UITableViewController *host) { id delegate = UIApplication.sharedApplication.delegate; if ([delegate respondsToSelector:@selector(window)]) { UIViewController *explorer = [self explorerViewControllerForObject: delegate.window.rootViewController ]; [host.navigationController pushViewController:explorer animated:YES]; } else { NSString *msg = @"The app delegate doesn't respond to -window"; [FLEXAlert showAlert:@":(" message:msg from:host]; } }; } default: return nil; } } @end