FLEXObjectExplorer.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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_explorerHidesPropertyIvars;
  79. BOOL hidePropertyMethods = defaults.flex_explorerHidesPropertyMethods;
  80. BOOL showMethodOverrides = defaults.flex_explorerShowsMethodOverrides;
  81. // Loop over each class and each superclass, collect
  82. // the fresh and unique metadata in each category
  83. Class superclass = nil;
  84. NSInteger count = self.classHierarchyClasses.count;
  85. NSInteger rootIdx = count - 1;
  86. for (NSInteger i = 0; i < count; i++) {
  87. Class cls = self.classHierarchyClasses[i];
  88. superclass = (i < rootIdx) ? self.classHierarchyClasses[i+1] : nil;
  89. [_allProperties addObject:[self
  90. metadataUniquedByName:[cls flex_allInstanceProperties]
  91. superclass:superclass
  92. kind:FLEXMetadataKindProperties
  93. skip:showMethodOverrides
  94. ]];
  95. [_allClassProperties addObject:[self
  96. metadataUniquedByName:[cls flex_allClassProperties]
  97. superclass:superclass
  98. kind:FLEXMetadataKindClassProperties
  99. skip:showMethodOverrides
  100. ]];
  101. [_allIvars addObject:[self
  102. metadataUniquedByName:[cls flex_allIvars]
  103. superclass:nil
  104. kind:FLEXMetadataKindIvars
  105. skip:NO
  106. ]];
  107. [_allMethods addObject:[self
  108. metadataUniquedByName:[cls flex_allInstanceMethods]
  109. superclass:superclass
  110. kind:FLEXMetadataKindMethods
  111. skip:showMethodOverrides
  112. ]];
  113. [_allClassMethods addObject:[self
  114. metadataUniquedByName:[cls flex_allClassMethods]
  115. superclass:superclass
  116. kind:FLEXMetadataKindClassMethods
  117. skip:showMethodOverrides
  118. ]];
  119. [_allConformedProtocols addObject:[self
  120. metadataUniquedByName:[cls flex_protocols]
  121. superclass:superclass
  122. kind:FLEXMetadataKindProtocols
  123. skip:NO
  124. ]];
  125. // TODO: join instance size, image name, and class hierarchy into a single model object
  126. // This would greatly reduce the laziness that has begun to manifest itself here
  127. [_allInstanceSizes addObject:[FLEXStaticMetadata
  128. style:FLEXStaticMetadataRowStyleKeyValue
  129. title:@"Instance Size" number:@(class_getInstanceSize(cls))
  130. ]];
  131. [_allImageNames addObject:[FLEXStaticMetadata
  132. style:FLEXStaticMetadataRowStyleDefault
  133. title:@"Image Name" string:@(class_getImageName(cls) ?: "Created at Runtime")
  134. ]];
  135. }
  136. _classHierarchy = [FLEXStaticMetadata classHierarchy:self.classHierarchyClasses];
  137. NSArray<NSArray<FLEXProperty *> *> *properties = _allProperties;
  138. // Potentially filter property-backing ivars
  139. if (hideBackingIvars) {
  140. NSArray<NSArray<FLEXIvar *> *> *ivars = _allIvars.copy;
  141. _allIvars = [ivars flex_mapped:^id(NSArray<FLEXIvar *> *list, NSUInteger idx) {
  142. // Get a set of all backing ivar names for the current class in the hierarchy
  143. NSSet *ivarNames = [NSSet setWithArray:({
  144. [properties[idx] flex_mapped:^id(FLEXProperty *p, NSUInteger idx) {
  145. // Nil if no ivar, and array is flatted
  146. return p.attributes.backingIvar;
  147. }];
  148. })];
  149. // Remove ivars whose name is in the ivar names list
  150. return [list flex_filtered:^BOOL(FLEXIvar *ivar, NSUInteger idx) {
  151. return ![ivarNames containsObject:ivar.name];
  152. }];
  153. }];
  154. }
  155. // Potentially filter property-backing methods
  156. if (hidePropertyMethods) {
  157. NSArray<NSArray<FLEXMethod *> *> *methods = _allMethods.copy;
  158. _allMethods = [methods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
  159. // Get a set of all property method names for the current class in the hierarchy
  160. NSSet *methodNames = [NSSet setWithArray:({
  161. [properties[idx] flex_flatmapped:^NSArray *(FLEXProperty *p, NSUInteger idx) {
  162. if (p.likelyGetterExists) {
  163. if (p.likelySetterExists) {
  164. return @[p.likelyGetterString, p.likelySetterString];
  165. }
  166. return @[p.likelyGetterString];
  167. } else if (p.likelySetterExists) {
  168. return @[p.likelySetterString];
  169. }
  170. return nil;
  171. }];
  172. })];
  173. // Remove ivars whose name is in the ivar names list
  174. return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
  175. return ![methodNames containsObject:method.selectorString];
  176. }];
  177. }];
  178. }
  179. // Set up UIKit helper data
  180. // Really, we only need to call this on properties and ivars
  181. // because no other metadata types support editing.
  182. for (NSArray *matrix in @[_allProperties, _allIvars, /* _allMethods, _allClassMethods, _allConformedProtocols */]) {
  183. for (NSArray *metadataByClass in matrix) {
  184. for (id<FLEXRuntimeMetadata> metadata in metadataByClass) {
  185. metadata.tag = metadata.isEditable ? @YES : nil;
  186. }
  187. }
  188. }
  189. [self reloadScopedMetadata];
  190. }
  191. #pragma mark - Private
  192. - (void)reloadScopedMetadata {
  193. _properties = self.allProperties[self.classScope];
  194. _classProperties = self.allClassProperties[self.classScope];
  195. _ivars = self.allIvars[self.classScope];
  196. _methods = self.allMethods[self.classScope];
  197. _classMethods = self.allClassMethods[self.classScope];
  198. _conformedProtocols = self.allConformedProtocols[self.classScope];
  199. _instanceSize = self.allInstanceSizes[self.classScope];
  200. _imageName = self.allImageNames[self.classScope];
  201. }
  202. /// Accepts an array of flex metadata objects and discards objects
  203. /// with duplicate names, as well as properties and methods which
  204. /// aren't "new" (i.e. those which the superclass responds to)
  205. - (NSArray *)metadataUniquedByName:(NSArray *)list
  206. superclass:(Class)superclass
  207. kind:(FLEXMetadataKind)kind
  208. skip:(BOOL)skip {
  209. if (skip) {
  210. return list;
  211. }
  212. // Remove items with same name and return filtered list
  213. NSMutableSet *names = [NSMutableSet new];
  214. return [list flex_filtered:^BOOL(id obj, NSUInteger idx) {
  215. NSString *name = [obj name];
  216. if ([names containsObject:name]) {
  217. return NO;
  218. } else {
  219. [names addObject:name];
  220. // Skip methods and properties which are just overrides,
  221. // potentially skip ivars and methods associated with properties
  222. switch (kind) {
  223. case FLEXMetadataKindProperties:
  224. if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
  225. return NO;
  226. }
  227. break;
  228. case FLEXMetadataKindClassProperties:
  229. if ([superclass respondsToSelector:[obj likelyGetter]]) {
  230. return NO;
  231. }
  232. break;
  233. case FLEXMetadataKindMethods:
  234. if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
  235. return NO;
  236. }
  237. break;
  238. case FLEXMetadataKindClassMethods:
  239. if ([superclass respondsToSelector:NSSelectorFromString(name)]) {
  240. return NO;
  241. }
  242. break;
  243. case FLEXMetadataKindProtocols:
  244. case FLEXMetadataKindClassHierarchy:
  245. case FLEXMetadataKindOther:
  246. return YES; // These types are already uniqued
  247. break;
  248. // Ivars cannot be overidden
  249. case FLEXMetadataKindIvars: break;
  250. }
  251. return YES;
  252. }
  253. }];
  254. }
  255. #pragma mark - Superclasses
  256. - (void)reloadClassHierarchy {
  257. // The class hierarchy will never contain metaclass objects by this logic;
  258. // it is always the same for a given class and instances of it
  259. _classHierarchyClasses = [[self.object class] flex_classHierarchy];
  260. }
  261. @end