FLEXInstancesViewController.m 9.7 KB

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