FLEXObjectExplorer.m 11 KB

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