FLEXObjectExplorerFactory.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //
  2. // FLEXObjectExplorerFactory.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 5/15/14.
  6. // Copyright (c) 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXObjectExplorerFactory.h"
  9. #import "FLEXGlobalsViewController.h"
  10. #import "FLEXClassShortcuts.h"
  11. #import "FLEXViewShortcuts.h"
  12. #import "FLEXViewControllerShortcuts.h"
  13. #import "FLEXUIAppShortcuts.h"
  14. #import "FLEXImageShortcuts.h"
  15. #import "FLEXLayerShortcuts.h"
  16. #import "FLEXColorPreviewSection.h"
  17. #import "FLEXDefaultsContentSection.h"
  18. #import "FLEXBundleShortcuts.h"
  19. #import "FLEXBlockShortcuts.h"
  20. #import "FLEXUtility.h"
  21. @implementation FLEXObjectExplorerFactory
  22. static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections = nil;
  23. + (void)initialize {
  24. if (self == [FLEXObjectExplorerFactory class]) {
  25. // DO NOT USE STRING KEYS HERE
  26. // We NEED to use the class as a key, because we CANNOT
  27. // differentiate a class's name from the metaclass's name.
  28. // These mappings are per-class-object, not per-class-name.
  29. //
  30. // For example, if we used class names, this would result in
  31. // the object explorer trying to render a color preview for
  32. // the UIColor class object, which is not a color itself.
  33. #define ClassKey(name) (id<NSCopying>)[name class]
  34. #define ClassKeyByName(str) (id<NSCopying>)NSClassFromString(@ #str)
  35. #define MetaclassKey(meta) (id<NSCopying>)object_getClass([meta class])
  36. classesToRegisteredSections = [NSMutableDictionary dictionaryWithDictionary:@{
  37. MetaclassKey(NSObject) : [FLEXClassShortcuts class],
  38. ClassKey(NSArray) : [FLEXCollectionContentSection class],
  39. ClassKey(NSSet) : [FLEXCollectionContentSection class],
  40. ClassKey(NSDictionary) : [FLEXCollectionContentSection class],
  41. ClassKey(NSOrderedSet) : [FLEXCollectionContentSection class],
  42. ClassKey(NSUserDefaults) : [FLEXDefaultsContentSection class],
  43. ClassKey(UIViewController) : [FLEXViewControllerShortcuts class],
  44. ClassKey(UIApplication) : [FLEXUIAppShortcuts class],
  45. ClassKey(UIView) : [FLEXViewShortcuts class],
  46. ClassKey(UIImage) : [FLEXImageShortcuts class],
  47. ClassKey(CALayer) : [FLEXLayerShortcuts class],
  48. ClassKey(UIColor) : [FLEXColorPreviewSection class],
  49. ClassKey(NSBundle) : [FLEXBundleShortcuts class],
  50. ClassKeyByName(NSBlock) : [FLEXBlockShortcuts class],
  51. }];
  52. #undef ClassKey
  53. #undef ClassKeyByName
  54. #undef MetaclassKey
  55. }
  56. }
  57. + (FLEXObjectExplorerViewController *)explorerViewControllerForObject:(id)object {
  58. // Can't explore nil
  59. if (!object) {
  60. return nil;
  61. }
  62. // If we're given an object, this will look up it's class hierarchy
  63. // until it finds a registration. This will work for KVC classes,
  64. // since they are children of the original class, and not siblings.
  65. // If we are given an object, object_getClass will return a metaclass,
  66. // and the same thing will happen. FLEXClassShortcuts is the default
  67. // shortcut section for NSObject.
  68. //
  69. // TODO: rename it to FLEXNSObjectShortcuts or something?
  70. Class sectionClass = nil;
  71. Class cls = object_getClass(object);
  72. do {
  73. sectionClass = classesToRegisteredSections[(id<NSCopying>)cls];
  74. } while (!sectionClass && (cls = [cls superclass]));
  75. if (!sectionClass) {
  76. sectionClass = [FLEXShortcutsSection class];
  77. }
  78. return [FLEXObjectExplorerViewController
  79. exploringObject:object
  80. customSection:[sectionClass forObject:object]
  81. ];
  82. }
  83. + (void)registerExplorerSection:(Class)explorerClass forClass:(Class)objectClass {
  84. classesToRegisteredSections[(id<NSCopying>)objectClass] = explorerClass;
  85. }
  86. #pragma mark - FLEXGlobalsEntry
  87. + (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
  88. switch (row) {
  89. case FLEXGlobalsRowAppDelegate:
  90. return @"🎟 App Delegate";
  91. case FLEXGlobalsRowKeyWindow:
  92. return @"🔑 Key Window";
  93. case FLEXGlobalsRowRootViewController:
  94. return @"🌴 Root View Controller";
  95. case FLEXGlobalsRowProcessInfo:
  96. return @"🚦 NSProcessInfo.processInfo";
  97. case FLEXGlobalsRowUserDefaults:
  98. return @"💾 Preferences";
  99. case FLEXGlobalsRowMainBundle:
  100. return @"📦 NSBundle.mainBundle";
  101. case FLEXGlobalsRowApplication:
  102. return @"🚀 UIApplication.sharedApplication";
  103. case FLEXGlobalsRowMainScreen:
  104. return @"💻 UIScreen.mainScreen";
  105. case FLEXGlobalsRowCurrentDevice:
  106. return @"📱 UIDevice.currentDevice";
  107. case FLEXGlobalsRowPasteboard:
  108. return @"📋 UIPasteboard.generalPasteboard";
  109. case FLEXGlobalsRowURLSession:
  110. return @"📡 NSURLSession.sharedSession";
  111. case FLEXGlobalsRowURLCache:
  112. return @"⏳ NSURLCache.sharedURLCache";
  113. case FLEXGlobalsRowNotificationCenter:
  114. return @"🔔 NSNotificationCenter.defaultCenter";
  115. case FLEXGlobalsRowMenuController:
  116. return @"📎 UIMenuController.sharedMenuController";
  117. case FLEXGlobalsRowFileManager:
  118. return @"🗄 NSFileManager.defaultManager";
  119. case FLEXGlobalsRowTimeZone:
  120. return @"🌎 NSTimeZone.systemTimeZone";
  121. case FLEXGlobalsRowLocale:
  122. return @"🗣 NSLocale.currentLocale";
  123. case FLEXGlobalsRowCalendar:
  124. return @"📅 NSCalendar.currentCalendar";
  125. case FLEXGlobalsRowMainRunLoop:
  126. return @"🏃🏻‍♂️ NSRunLoop.mainRunLoop";
  127. case FLEXGlobalsRowMainThread:
  128. return @"🧵 NSThread.mainThread";
  129. case FLEXGlobalsRowOperationQueue:
  130. return @"📚 NSOperationQueue.mainQueue";
  131. default: return nil;
  132. }
  133. }
  134. + (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
  135. switch (row) {
  136. case FLEXGlobalsRowAppDelegate: {
  137. id<UIApplicationDelegate> appDelegate = UIApplication.sharedApplication.delegate;
  138. return [self explorerViewControllerForObject:appDelegate];
  139. }
  140. case FLEXGlobalsRowProcessInfo:
  141. return [self explorerViewControllerForObject:NSProcessInfo.processInfo];
  142. case FLEXGlobalsRowUserDefaults:
  143. return [self explorerViewControllerForObject:NSUserDefaults.standardUserDefaults];
  144. case FLEXGlobalsRowMainBundle:
  145. return [self explorerViewControllerForObject:NSBundle.mainBundle];
  146. case FLEXGlobalsRowApplication:
  147. return [self explorerViewControllerForObject:UIApplication.sharedApplication];
  148. case FLEXGlobalsRowMainScreen:
  149. return [self explorerViewControllerForObject:UIScreen.mainScreen];
  150. case FLEXGlobalsRowCurrentDevice:
  151. return [self explorerViewControllerForObject:UIDevice.currentDevice];
  152. case FLEXGlobalsRowPasteboard:
  153. #if !TARGET_OS_TV
  154. return [self explorerViewControllerForObject:UIPasteboard.generalPasteboard];
  155. #else
  156. return nil; //FIXME: Probably not a safe fix
  157. #endif
  158. case FLEXGlobalsRowURLSession:
  159. return [self explorerViewControllerForObject:NSURLSession.sharedSession];
  160. case FLEXGlobalsRowURLCache:
  161. return [self explorerViewControllerForObject:NSURLCache.sharedURLCache];
  162. case FLEXGlobalsRowNotificationCenter:
  163. return [self explorerViewControllerForObject:NSNotificationCenter.defaultCenter];
  164. case FLEXGlobalsRowMenuController:
  165. #if !TARGET_OS_TV
  166. return [self explorerViewControllerForObject:UIMenuController.sharedMenuController];
  167. #else
  168. return nil; //FIXME: Probably not a safe fix
  169. #endif
  170. case FLEXGlobalsRowFileManager:
  171. return [self explorerViewControllerForObject:NSFileManager.defaultManager];
  172. case FLEXGlobalsRowTimeZone:
  173. return [self explorerViewControllerForObject:NSTimeZone.systemTimeZone];
  174. case FLEXGlobalsRowLocale:
  175. return [self explorerViewControllerForObject:NSLocale.currentLocale];
  176. case FLEXGlobalsRowCalendar:
  177. return [self explorerViewControllerForObject:NSCalendar.currentCalendar];
  178. case FLEXGlobalsRowMainRunLoop:
  179. return [self explorerViewControllerForObject:NSRunLoop.mainRunLoop];
  180. case FLEXGlobalsRowMainThread:
  181. return [self explorerViewControllerForObject:NSThread.mainThread];
  182. case FLEXGlobalsRowOperationQueue:
  183. return [self explorerViewControllerForObject:NSOperationQueue.mainQueue];
  184. case FLEXGlobalsRowKeyWindow:
  185. return [FLEXObjectExplorerFactory
  186. explorerViewControllerForObject:FLEXUtility.appKeyWindow
  187. ];
  188. case FLEXGlobalsRowRootViewController: {
  189. id<UIApplicationDelegate> delegate = UIApplication.sharedApplication.delegate;
  190. if ([delegate respondsToSelector:@selector(window)]) {
  191. return [self explorerViewControllerForObject:delegate.window.rootViewController];
  192. }
  193. return nil;
  194. }
  195. default: return nil;
  196. }
  197. }
  198. + (FLEXGlobalsEntryRowAction)globalsEntryRowAction:(FLEXGlobalsRow)row {
  199. switch (row) {
  200. case FLEXGlobalsRowRootViewController: {
  201. // Check if the app delegate responds to -window. If not, present an alert
  202. return ^(UITableViewController *host) {
  203. id<UIApplicationDelegate> delegate = UIApplication.sharedApplication.delegate;
  204. if ([delegate respondsToSelector:@selector(window)]) {
  205. UIViewController *explorer = [self explorerViewControllerForObject:
  206. delegate.window.rootViewController
  207. ];
  208. [host.navigationController pushViewController:explorer animated:YES];
  209. } else {
  210. NSString *msg = @"The app delegate doesn't respond to -window";
  211. [FLEXAlert showAlert:@":(" message:msg from:host];
  212. }
  213. };
  214. }
  215. default: return nil;
  216. }
  217. }
  218. @end