FLEXObjectExplorer.m 11 KB

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