FLEXObjectListViewController.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. //
  2. // FLEXObjectListViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 5/28/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXObjectListViewController.h"
  9. #import "FLEXObjectExplorerFactory.h"
  10. #import "FLEXObjectExplorerViewController.h"
  11. #import "FLEXCollectionContentSection.h"
  12. #import "FLEXRuntimeUtility.h"
  13. #import "FLEXUtility.h"
  14. #import "FLEXHeapEnumerator.h"
  15. #import "FLEXObjectRef.h"
  16. #import "NSString+FLEX.h"
  17. #import "NSObject+Reflection.h"
  18. #import "FLEXTableViewCell.h"
  19. #import <malloc/malloc.h>
  20. @interface FLEXObjectListViewController ()
  21. @property (nonatomic) NSArray<FLEXCollectionContentSection *> *sections;
  22. @property (nonatomic, readonly) NSArray<FLEXCollectionContentSection *> *allSections;
  23. /// Array of [[section], [section], ...]
  24. /// where [section] is [["row title", instance], ["row title", instance], ...]
  25. @property (nonatomic) NSArray<FLEXObjectRef *> *references;
  26. @end
  27. @implementation FLEXObjectListViewController
  28. #pragma mark - Reference Grouping
  29. + (NSPredicate *)defaultPredicateForSection:(NSInteger)section {
  30. // These are the types of references that we typically don't care about.
  31. // We want this list of "object-ivar pairs" split into two sections.
  32. BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  33. NSString *row = ref.reference;
  34. return [row isEqualToString:@"__NSObserver object"] ||
  35. [row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
  36. };
  37. /// These are common AutoLayout related references we also rarely care about.
  38. BOOL(^isConstraintRelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  39. static NSSet *ignored = nil;
  40. static dispatch_once_t onceToken;
  41. dispatch_once(&onceToken, ^{
  42. ignored = [NSSet setWithArray:@[
  43. @"NSLayoutConstraint _container",
  44. @"NSContentSizeLayoutConstraint _container",
  45. @"NSAutoresizingMaskLayoutConstraint _container",
  46. @"MASViewConstraint _installedView",
  47. @"MASLayoutConstraint _container",
  48. @"MASViewAttribute _view"
  49. ]];
  50. });
  51. NSString *row = ref.reference;
  52. return ([row hasPrefix:@"NSLayout"] && [row hasSuffix:@" _referenceItem"]) ||
  53. ([row hasPrefix:@"NSIS"] && [row hasSuffix:@" _delegate"]) ||
  54. ([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
  55. [ignored containsObject:row];
  56. };
  57. BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  58. return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
  59. };
  60. switch (section) {
  61. case 0: return [NSPredicate predicateWithBlock:isEssential];
  62. case 1: return [NSPredicate predicateWithBlock:isConstraintRelated];
  63. case 2: return [NSPredicate predicateWithBlock:isObserver];
  64. default: return nil;
  65. }
  66. }
  67. + (NSArray<NSPredicate *> *)defaultPredicates {
  68. return @[[self defaultPredicateForSection:0],
  69. [self defaultPredicateForSection:1],
  70. [self defaultPredicateForSection:2]];
  71. }
  72. + (NSArray<NSString *> *)defaultSectionTitles {
  73. return @[@"", @"AutoLayout", @"Trivial"];
  74. }
  75. #pragma mark - Initialization
  76. - (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references {
  77. return [self initWithReferences:references predicates:nil sectionTitles:nil];
  78. }
  79. - (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references
  80. predicates:(NSArray<NSPredicate *> *)predicates
  81. sectionTitles:(NSArray<NSString *> *)sectionTitles {
  82. NSParameterAssert(predicates.count == sectionTitles.count);
  83. self = [super initWithStyle:UITableViewStylePlain];
  84. if (self) {
  85. self.references = references;
  86. if (predicates.count) {
  87. [self buildSections:sectionTitles predicates:predicates];
  88. } else {
  89. _sections = _allSections = @[[self makeSection:references title:nil]];
  90. }
  91. }
  92. return self;
  93. }
  94. + (instancetype)instancesOfClassWithName:(NSString *)className {
  95. const char *classNameCString = className.UTF8String;
  96. NSMutableArray *instances = [NSMutableArray array];
  97. [FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
  98. if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
  99. // Note: objects of certain classes crash when retain is called.
  100. // It is up to the user to avoid tapping into instance lists for these classes.
  101. // Ex. OS_dispatch_queue_specific_queue
  102. // In the future, we could provide some kind of warning for classes that are known to be problematic.
  103. if (malloc_size((__bridge const void *)(object)) > 0) {
  104. [instances addObject:object];
  105. }
  106. }
  107. }];
  108. NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
  109. FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
  110. controller.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)instances.count];
  111. return controller;
  112. }
  113. + (instancetype)subclassesOfClassWithName:(NSString *)className {
  114. NSArray<Class> *classes = FLEXGetAllSubclasses(NSClassFromString(className), NO);
  115. NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingClasses:classes];
  116. FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references];
  117. controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%lu)",
  118. className, (unsigned long)classes.count
  119. ];
  120. return controller;
  121. }
  122. + (instancetype)objectsWithReferencesToObject:(id)object {
  123. static Class SwiftObjectClass = nil;
  124. static dispatch_once_t onceToken;
  125. dispatch_once(&onceToken, ^{
  126. SwiftObjectClass = NSClassFromString(@"SwiftObject");
  127. if (!SwiftObjectClass) {
  128. SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject");
  129. }
  130. });
  131. NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray array];
  132. [FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
  133. // Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
  134. // Once we find a match, record it and move on to the next object. There's no reason to find multiple matches within the same object.
  135. Class tryClass = actualClass;
  136. while (tryClass) {
  137. unsigned int ivarCount = 0;
  138. Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
  139. for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
  140. Ivar ivar = ivars[ivarIndex];
  141. NSString *typeEncoding = @(ivar_getTypeEncoding(ivar) ?: "");
  142. if (typeEncoding.flex_typeIsObjectOrClass) {
  143. ptrdiff_t offset = ivar_getOffset(ivar);
  144. uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
  145. if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
  146. NSString *ivarName = @(ivar_getName(ivar) ?: "???");
  147. [instances addObject:[FLEXObjectRef referencing:tryObject ivar:ivarName]];
  148. return;
  149. }
  150. }
  151. }
  152. tryClass = class_getSuperclass(tryClass);
  153. }
  154. }];
  155. NSArray<NSPredicate *> *predicates = [self defaultPredicates];
  156. NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
  157. FLEXObjectListViewController *viewController = [[self alloc]
  158. initWithReferences:instances
  159. predicates:predicates
  160. sectionTitles:sectionTitles
  161. ];
  162. viewController.title = [NSString stringWithFormat:@"Referencing %@ %p",
  163. NSStringFromClass(object_getClass(object)), object
  164. ];
  165. return viewController;
  166. }
  167. #pragma mark - Lifecycle
  168. - (void)viewDidLoad {
  169. [super viewDidLoad];
  170. self.showsSearchBar = YES;
  171. }
  172. #pragma mark - Private
  173. - (void)buildSections:(NSArray<NSString *> *)titles predicates:(NSArray<NSPredicate *> *)predicates {
  174. NSParameterAssert(titles.count == predicates.count);
  175. NSParameterAssert(titles); NSParameterAssert(predicates);
  176. _sections = _allSections = [NSArray flex_forEachUpTo:titles.count map:^id(NSUInteger i) {
  177. NSArray *rows = [self.references filteredArrayUsingPredicate:predicates[i]];
  178. return [self makeSection:rows title:titles[i]];
  179. }];
  180. }
  181. - (FLEXCollectionContentSection *)makeSection:(NSArray *)rows title:(NSString *)title {
  182. FLEXCollectionContentSection *section = [FLEXCollectionContentSection forCollection:rows];
  183. // We need custom filtering because we do custom cell configuration
  184. section.customFilter = ^BOOL(NSString *filterText, FLEXObjectRef *ref) {
  185. if (ref.summary && [ref.summary localizedCaseInsensitiveContainsString:filterText]) {
  186. return YES;
  187. }
  188. return [ref.reference localizedCaseInsensitiveContainsString:filterText];
  189. };
  190. // Use custom title, or hide title entirely
  191. if (title) {
  192. section.customTitle = title;
  193. } else {
  194. section.hideSectionTitle = YES;
  195. }
  196. return section;
  197. }
  198. - (NSArray *)nonemptySections {
  199. return [self.allSections flex_filtered:^BOOL(FLEXTableViewSection *section, NSUInteger idx) {
  200. return section.numberOfRows > 0;
  201. }];
  202. }
  203. - (FLEXObjectRef *)referenceForIndexPath:(NSIndexPath *)ip {
  204. return [self.sections[ip.section] objectForRow:ip.row];
  205. }
  206. #pragma mark - Search
  207. - (void)updateSearchResults:(NSString *)newText; {
  208. // Sections will adjust data based on this property
  209. for (FLEXTableViewSection *section in self.allSections) {
  210. section.filterText = newText;
  211. }
  212. // Recalculate empty sections
  213. self.sections = [self nonemptySections];
  214. // Refresh table view
  215. if (self.isViewLoaded) {
  216. [self.tableView reloadData];
  217. }
  218. }
  219. #pragma mark - Table View Data Source
  220. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  221. return self.sections.count;
  222. }
  223. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  224. return self.sections[section].numberOfRows;
  225. }
  226. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  227. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXDetailCell forIndexPath:indexPath];
  228. FLEXObjectRef *ref = [self referenceForIndexPath:indexPath];
  229. cell.textLabel.text = ref.reference;
  230. cell.detailTextLabel.text = ref.summary;
  231. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  232. return cell;
  233. }
  234. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  235. return self.sections[section].title;
  236. }
  237. #pragma mark - Table View Delegate
  238. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  239. [self.navigationController pushViewController:[FLEXObjectExplorerFactory
  240. explorerViewControllerForObject:[self referenceForIndexPath:indexPath].object
  241. ] animated:YES];
  242. }
  243. @end