FLEXInstancesTableViewController.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. //
  2. // FLEXInstancesTableViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 5/28/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXInstancesTableViewController.h"
  9. #import "FLEXObjectExplorerFactory.h"
  10. #import "FLEXObjectExplorerViewController.h"
  11. #import "FLEXRuntimeUtility.h"
  12. #import "FLEXUtility.h"
  13. #import "FLEXHeapEnumerator.h"
  14. #import "FLEXObjectRef.h"
  15. #import <malloc/malloc.h>
  16. @interface FLEXInstancesTableViewController ()
  17. /// Array of [[section], [section], ...]
  18. /// where [section] is [["row title", instance], ["row title", instance], ...]
  19. @property (nonatomic) NSArray<FLEXObjectRef *> *instances;
  20. @property (nonatomic) NSArray<NSArray<FLEXObjectRef*>*> *sections;
  21. @property (nonatomic) NSArray<NSString *> *sectionTitles;
  22. @property (nonatomic) NSArray<NSPredicate *> *predicates;
  23. @property (nonatomic, readonly) NSInteger maxSections;
  24. @end
  25. @implementation FLEXInstancesTableViewController
  26. - (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references {
  27. return [self initWithReferences:references predicates:nil sectionTitles:nil];
  28. }
  29. - (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references
  30. predicates:(NSArray<NSPredicate *> *)predicates
  31. sectionTitles:(NSArray<NSString *> *)sectionTitles {
  32. NSParameterAssert(predicates.count == sectionTitles.count);
  33. self = [super init];
  34. if (self) {
  35. self.instances = references;
  36. self.predicates = predicates;
  37. self.sectionTitles = sectionTitles;
  38. if (predicates.count) {
  39. [self buildSections];
  40. } else {
  41. self.sections = @[references];
  42. }
  43. }
  44. return self;
  45. }
  46. + (instancetype)instancesTableViewControllerForClassName:(NSString *)className {
  47. const char *classNameCString = className.UTF8String;
  48. NSMutableArray *instances = [NSMutableArray array];
  49. [FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
  50. if (strcmp(classNameCString, class_getName(actualClass)) == 0) {
  51. // Note: objects of certain classes crash when retain is called.
  52. // It is up to the user to avoid tapping into instance lists for these classes.
  53. // Ex. OS_dispatch_queue_specific_queue
  54. // In the future, we could provide some kind of warning for classes that are known to be problematic.
  55. if (malloc_size((__bridge const void *)(object)) > 0) {
  56. [instances addObject:object];
  57. }
  58. }
  59. }];
  60. NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
  61. FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:references];
  62. viewController.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)instances.count];
  63. return viewController;
  64. }
  65. + (instancetype)instancesTableViewControllerForInstancesReferencingObject:(id)object {
  66. NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray array];
  67. [FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
  68. // Skip Swift objects
  69. if ([actualClass isKindOfClass:NSClassFromString(@"SwiftObject")]) {
  70. return;
  71. }
  72. // Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
  73. // 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.
  74. Class tryClass = actualClass;
  75. while (tryClass) {
  76. unsigned int ivarCount = 0;
  77. Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
  78. for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
  79. Ivar ivar = ivars[ivarIndex];
  80. const char *typeEncoding = ivar_getTypeEncoding(ivar);
  81. if (typeEncoding[0] == FLEXTypeEncodingObjcObject || typeEncoding[0] == FLEXTypeEncodingObjcClass) {
  82. ptrdiff_t offset = ivar_getOffset(ivar);
  83. uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
  84. if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
  85. [instances addObject:[FLEXObjectRef referencing:tryObject ivar:@(ivar_getName(ivar))]];
  86. return;
  87. }
  88. }
  89. }
  90. tryClass = class_getSuperclass(tryClass);
  91. }
  92. }];
  93. NSArray<NSPredicate *> *predicates = [self defaultPredicates];
  94. NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
  95. FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:instances
  96. predicates:predicates
  97. sectionTitles:sectionTitles];
  98. viewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
  99. return viewController;
  100. }
  101. + (NSPredicate *)defaultPredicateForSection:(NSInteger)section {
  102. // These are the types of references that we typically don't care about.
  103. // We want this list of "object-ivar pairs" split into two sections.
  104. BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  105. NSString *row = ref.reference;
  106. return [row isEqualToString:@"__NSObserver object"] ||
  107. [row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
  108. };
  109. /// These are common AutoLayout related references we also rarely care about.
  110. BOOL(^isConstraintRelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  111. static NSSet *ignored = nil;
  112. static dispatch_once_t onceToken;
  113. dispatch_once(&onceToken, ^{
  114. ignored = [NSSet setWithArray:@[
  115. @"NSLayoutConstraint _container",
  116. @"NSContentSizeLayoutConstraint _container",
  117. @"NSAutoresizingMaskLayoutConstraint _container",
  118. @"MASViewConstraint _installedView",
  119. @"MASLayoutConstraint _container",
  120. @"MASViewAttribute _view"
  121. ]];
  122. });
  123. NSString *row = ref.reference;
  124. return ([row hasPrefix:@"NSLayout"] && [row hasSuffix:@" _referenceItem"]) ||
  125. ([row hasPrefix:@"NSIS"] && [row hasSuffix:@" _delegate"]) ||
  126. ([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
  127. [ignored containsObject:row];
  128. };
  129. BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  130. return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
  131. };
  132. switch (section) {
  133. case 0: return [NSPredicate predicateWithBlock:isEssential];
  134. case 1: return [NSPredicate predicateWithBlock:isConstraintRelated];
  135. case 2: return [NSPredicate predicateWithBlock:isObserver];
  136. default: return nil;
  137. }
  138. }
  139. + (NSArray<NSPredicate *> *)defaultPredicates {
  140. return @[[self defaultPredicateForSection:0],
  141. [self defaultPredicateForSection:1],
  142. [self defaultPredicateForSection:2]];
  143. }
  144. + (NSArray<NSString *> *)defaultSectionTitles {
  145. return @[@"", @"AutoLayout", @"Trivial"];
  146. }
  147. - (void)buildSections {
  148. NSInteger maxSections = self.maxSections;
  149. NSMutableArray *sections = [NSMutableArray array];
  150. for (NSInteger i = 0; i < maxSections; i++) {
  151. NSPredicate *predicate = self.predicates[i];
  152. [sections addObject:[self.instances filteredArrayUsingPredicate:predicate]];
  153. }
  154. self.sections = sections;
  155. }
  156. - (NSInteger)maxSections {
  157. return self.predicates.count ?: 1;
  158. }
  159. #pragma mark - Table View Data Source
  160. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  161. return self.maxSections;
  162. }
  163. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  164. return self.sections[section].count;
  165. }
  166. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  167. static NSString *CellIdentifier = @"Cell";
  168. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  169. if (!cell) {
  170. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
  171. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  172. UIFont *cellFont = UIFont.flex_defaultTableCellFont;
  173. cell.textLabel.font = cellFont;
  174. cell.detailTextLabel.font = cellFont;
  175. cell.detailTextLabel.textColor = UIColor.grayColor;
  176. }
  177. FLEXObjectRef *row = self.sections[indexPath.section][indexPath.row];
  178. cell.textLabel.text = row.reference;
  179. cell.detailTextLabel.text = [FLEXRuntimeUtility summaryForObject:row.object];
  180. return cell;
  181. }
  182. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  183. if (self.sectionTitles.count) {
  184. // Return nil instead of empty strings
  185. NSString *title = self.sectionTitles[section];
  186. if (title.length) {
  187. return title;
  188. }
  189. }
  190. return nil;
  191. }
  192. #pragma mark - Table View Delegate
  193. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  194. id instance = self.instances[indexPath.row].object;
  195. FLEXObjectExplorerViewController *drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:instance];
  196. [self.navigationController pushViewController:drillInViewController animated:YES];
  197. }
  198. @end