FLEXObjectExplorer.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. //
  2. // FLEXObjectExplorer.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 8/28/19.
  6. // Copyright © 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXObjectExplorer.h"
  9. #import "FLEXUtility.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "NSObject+FLEX_Reflection.h"
  12. #import "FLEXRuntime+Compare.h"
  13. #import "FLEXRuntime+UIKitHelpers.h"
  14. #import "FLEXPropertyAttributes.h"
  15. #import "FLEXMetadataSection.h"
  16. #import "NSUserDefaults+FLEX.h"
  17. @implementation FLEXObjectExplorerDefaults
  18. + (instancetype)canEdit:(BOOL)editable wantsPreviews:(BOOL)showPreviews {
  19. FLEXObjectExplorerDefaults *defaults = [self new];
  20. defaults->_isEditable = editable;
  21. defaults->_wantsDynamicPreviews = showPreviews;
  22. return defaults;
  23. }
  24. @end
  25. @interface FLEXObjectExplorer () {
  26. NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
  27. NSMutableArray<NSArray<FLEXProperty *> *> *_allClassProperties;
  28. NSMutableArray<NSArray<FLEXIvar *> *> *_allIvars;
  29. NSMutableArray<NSArray<FLEXMethod *> *> *_allMethods;
  30. NSMutableArray<NSArray<FLEXMethod *> *> *_allClassMethods;
  31. NSMutableArray<NSArray<FLEXProtocol *> *> *_allConformedProtocols;
  32. NSMutableArray<FLEXStaticMetadata *> *_allInstanceSizes;
  33. NSMutableArray<FLEXStaticMetadata *> *_allImageNames;
  34. NSString *_objectDescription;
  35. }
  36. @end
  37. @implementation FLEXObjectExplorer
  38. #pragma mark - Initialization
  39. + (id)forObject:(id)objectOrClass {
  40. return [[self alloc] initWithObject:objectOrClass];
  41. }
  42. - (id)initWithObject:(id)objectOrClass {
  43. NSParameterAssert(objectOrClass);
  44. self = [super init];
  45. if (self) {
  46. _object = objectOrClass;
  47. _objectIsInstance = !object_isClass(objectOrClass);
  48. [self reloadMetadata];
  49. }
  50. return self;
  51. }
  52. #pragma mark - Public
  53. + (void)configureDefaultsForItems:(NSArray<id<FLEXObjectExplorerItem>> *)items {
  54. BOOL hidePreviews = NSUserDefaults.standardUserDefaults.flex_explorerHidesVariablePreviews;
  55. FLEXObjectExplorerDefaults *mutable = [FLEXObjectExplorerDefaults
  56. canEdit:YES wantsPreviews:!hidePreviews
  57. ];
  58. FLEXObjectExplorerDefaults *immutable = [FLEXObjectExplorerDefaults
  59. canEdit:NO wantsPreviews:!hidePreviews
  60. ];
  61. // .tag is used to cache whether the value of .isEditable;
  62. // This could change at runtime so it is important that
  63. // it is cached every time shortcuts are requeted and not
  64. // just once at as shortcuts are initially registered
  65. for (id<FLEXObjectExplorerItem> metadata in items) {
  66. metadata.defaults = metadata.isEditable ? mutable : immutable;
  67. }
  68. }
  69. - (NSString *)objectDescription {
  70. if (!_objectDescription) {
  71. // Hard-code UIColor description
  72. if ([FLEXRuntimeUtility safeObject:self.object isKindOfClass:[UIColor class]]) {
  73. CGFloat h, s, l, r, g, b, a;
  74. [self.object getRed:&r green:&g blue:&b alpha:&a];
  75. [self.object getHue:&h saturation:&s brightness:&l alpha:nil];
  76. return [NSString stringWithFormat:
  77. @"HSL: (%.3f, %.3f, %.3f)\nRGB: (%.3f, %.3f, %.3f)\nAlpha: %.3f",
  78. h, s, l, r, g, b, a
  79. ];
  80. }
  81. NSString *description = [FLEXRuntimeUtility safeDescriptionForObject:self.object];
  82. if (!description.length) {
  83. NSString *address = [FLEXUtility addressOfObject:self.object];
  84. return [NSString stringWithFormat:@"Object at %@ returned empty description", address];
  85. }
  86. if (description.length > 10000) {
  87. description = [description substringToIndex:10000];
  88. }
  89. _objectDescription = description;
  90. }
  91. return _objectDescription;
  92. }
  93. - (void)setClassScope:(NSInteger)classScope {
  94. _classScope = classScope;
  95. [self reloadScopedMetadata];
  96. }
  97. - (void)reloadMetadata {
  98. _allProperties = [NSMutableArray new];
  99. _allClassProperties = [NSMutableArray new];
  100. _allIvars = [NSMutableArray new];
  101. _allMethods = [NSMutableArray new];
  102. _allClassMethods = [NSMutableArray new];
  103. _allConformedProtocols = [NSMutableArray new];
  104. _allInstanceSizes = [NSMutableArray new];
  105. _allImageNames = [NSMutableArray new];
  106. _objectDescription = nil;
  107. [self reloadClassHierarchy];
  108. NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
  109. BOOL hideBackingIvars = defaults.flex_explorerHidesPropertyIvars;
  110. BOOL hidePropertyMethods = defaults.flex_explorerHidesPropertyMethods;
  111. BOOL showMethodOverrides = defaults.flex_explorerShowsMethodOverrides;
  112. // Loop over each class and each superclass, collect
  113. // the fresh and unique metadata in each category
  114. Class superclass = nil;
  115. NSInteger count = self.classHierarchyClasses.count;
  116. NSInteger rootIdx = count - 1;
  117. for (NSInteger i = 0; i < count; i++) {
  118. Class cls = self.classHierarchyClasses[i];
  119. superclass = (i < rootIdx) ? self.classHierarchyClasses[i+1] : nil;
  120. [_allProperties addObject:[self
  121. metadataUniquedByName:[cls flex_allInstanceProperties]
  122. superclass:superclass
  123. kind:FLEXMetadataKindProperties
  124. skip:showMethodOverrides
  125. ]];
  126. [_allClassProperties addObject:[self
  127. metadataUniquedByName:[cls flex_allClassProperties]
  128. superclass:superclass
  129. kind:FLEXMetadataKindClassProperties
  130. skip:showMethodOverrides
  131. ]];
  132. [_allIvars addObject:[self
  133. metadataUniquedByName:[cls flex_allIvars]
  134. superclass:nil
  135. kind:FLEXMetadataKindIvars
  136. skip:NO
  137. ]];
  138. [_allMethods addObject:[self
  139. metadataUniquedByName:[cls flex_allInstanceMethods]
  140. superclass:superclass
  141. kind:FLEXMetadataKindMethods
  142. skip:showMethodOverrides
  143. ]];
  144. [_allClassMethods addObject:[self
  145. metadataUniquedByName:[cls flex_allClassMethods]
  146. superclass:superclass
  147. kind:FLEXMetadataKindClassMethods
  148. skip:showMethodOverrides
  149. ]];
  150. [_allConformedProtocols addObject:[self
  151. metadataUniquedByName:[cls flex_protocols]
  152. superclass:superclass
  153. kind:FLEXMetadataKindProtocols
  154. skip:NO
  155. ]];
  156. // TODO: join instance size, image name, and class hierarchy into a single model object
  157. // This would greatly reduce the laziness that has begun to manifest itself here
  158. [_allInstanceSizes addObject:[FLEXStaticMetadata
  159. style:FLEXStaticMetadataRowStyleKeyValue
  160. title:@"Instance Size" number:@(class_getInstanceSize(cls))
  161. ]];
  162. [_allImageNames addObject:[FLEXStaticMetadata
  163. style:FLEXStaticMetadataRowStyleDefault
  164. title:@"Image Name" string:@(class_getImageName(cls) ?: "Created at Runtime")
  165. ]];
  166. }
  167. _classHierarchy = [FLEXStaticMetadata classHierarchy:self.classHierarchyClasses];
  168. NSArray<NSArray<FLEXProperty *> *> *properties = _allProperties;
  169. // Potentially filter property-backing ivars
  170. if (hideBackingIvars) {
  171. NSArray<NSArray<FLEXIvar *> *> *ivars = _allIvars.copy;
  172. _allIvars = [ivars flex_mapped:^id(NSArray<FLEXIvar *> *list, NSUInteger idx) {
  173. // Get a set of all backing ivar names for the current class in the hierarchy
  174. NSSet *ivarNames = [NSSet setWithArray:({
  175. [properties[idx] flex_mapped:^id(FLEXProperty *p, NSUInteger idx) {
  176. // Nil if no ivar, and array is flatted
  177. return p.attributes.backingIvar;
  178. }];
  179. })];
  180. // Remove ivars whose name is in the ivar names list
  181. return [list flex_filtered:^BOOL(FLEXIvar *ivar, NSUInteger idx) {
  182. return ![ivarNames containsObject:ivar.name];
  183. }];
  184. }];
  185. }
  186. // Potentially filter property-backing methods
  187. if (hidePropertyMethods) {
  188. NSArray<NSArray<FLEXMethod *> *> *methods = _allMethods.copy;
  189. _allMethods = [methods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
  190. // Get a set of all property method names for the current class in the hierarchy
  191. NSSet *methodNames = [NSSet setWithArray:({
  192. [properties[idx] flex_flatmapped:^NSArray *(FLEXProperty *p, NSUInteger idx) {
  193. if (p.likelyGetterExists) {
  194. if (p.likelySetterExists) {
  195. return @[p.likelyGetterString, p.likelySetterString];
  196. }
  197. return @[p.likelyGetterString];
  198. } else if (p.likelySetterExists) {
  199. return @[p.likelySetterString];
  200. }
  201. return nil;
  202. }];
  203. })];
  204. // Remove ivars whose name is in the ivar names list
  205. return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
  206. return ![methodNames containsObject:method.selectorString];
  207. }];
  208. }];
  209. }
  210. // Set up UIKit helper data
  211. // Really, we only need to call this on properties and ivars
  212. // because no other metadata types support editing.
  213. NSArray<NSArray *>*metadatas = @[
  214. _allProperties, _allClassProperties, _allIvars,
  215. /* _allMethods, _allClassMethods, _allConformedProtocols */
  216. ];
  217. for (NSArray *matrix in metadatas) {
  218. for (NSArray *metadataByClass in matrix) {
  219. [FLEXObjectExplorer configureDefaultsForItems:metadataByClass];
  220. }
  221. }
  222. [self reloadScopedMetadata];
  223. }
  224. #pragma mark - Private
  225. - (void)reloadScopedMetadata {
  226. _properties = self.allProperties[self.classScope];
  227. _classProperties = self.allClassProperties[self.classScope];
  228. _ivars = self.allIvars[self.classScope];
  229. _methods = self.allMethods[self.classScope];
  230. _classMethods = self.allClassMethods[self.classScope];
  231. _conformedProtocols = self.allConformedProtocols[self.classScope];
  232. _instanceSize = self.allInstanceSizes[self.classScope];
  233. _imageName = self.allImageNames[self.classScope];
  234. }
  235. /// Accepts an array of flex metadata objects and discards objects
  236. /// with duplicate names, as well as properties and methods which
  237. /// aren't "new" (i.e. those which the superclass responds to)
  238. - (NSArray *)metadataUniquedByName:(NSArray *)list
  239. superclass:(Class)superclass
  240. kind:(FLEXMetadataKind)kind
  241. skip:(BOOL)skip {
  242. if (skip) {
  243. return list;
  244. }
  245. // Remove items with same name and return filtered list
  246. NSMutableSet *names = [NSMutableSet new];
  247. return [list flex_filtered:^BOOL(id obj, NSUInteger idx) {
  248. NSString *name = [obj name];
  249. if ([names containsObject:name]) {
  250. return NO;
  251. } else {
  252. [names addObject:name];
  253. // Skip methods and properties which are just overrides,
  254. // potentially skip ivars and methods associated with properties
  255. switch (kind) {
  256. case FLEXMetadataKindProperties:
  257. if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
  258. return NO;
  259. }
  260. break;
  261. case FLEXMetadataKindClassProperties:
  262. if ([superclass respondsToSelector:[obj likelyGetter]]) {
  263. return NO;
  264. }
  265. break;
  266. case FLEXMetadataKindMethods:
  267. if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
  268. return NO;
  269. }
  270. break;
  271. case FLEXMetadataKindClassMethods:
  272. if ([superclass respondsToSelector:NSSelectorFromString(name)]) {
  273. return NO;
  274. }
  275. break;
  276. case FLEXMetadataKindProtocols:
  277. case FLEXMetadataKindClassHierarchy:
  278. case FLEXMetadataKindOther:
  279. return YES; // These types are already uniqued
  280. break;
  281. // Ivars cannot be overidden
  282. case FLEXMetadataKindIvars: break;
  283. }
  284. return YES;
  285. }
  286. }];
  287. }
  288. #pragma mark - Superclasses
  289. - (void)reloadClassHierarchy {
  290. // The class hierarchy will never contain metaclass objects by this logic;
  291. // it is always the same for a given class and instances of it
  292. _classHierarchyClasses = [[self.object class] flex_classHierarchy];
  293. }
  294. @end