You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
9.6 KiB

  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. {
  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. FLEXInstancesTableViewController *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. {
  68. NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray array];
  69. [FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
  70. // Skip Swift objects
  71. if ([actualClass isKindOfClass:NSClassFromString(@"SwiftObject")]) {
  72. return;
  73. }
  74. // Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
  75. // 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.
  76. Class tryClass = actualClass;
  77. while (tryClass) {
  78. unsigned int ivarCount = 0;
  79. Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
  80. for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
  81. Ivar ivar = ivars[ivarIndex];
  82. const char *typeEncoding = ivar_getTypeEncoding(ivar);
  83. if (typeEncoding[0] == FLEXTypeEncodingObjcObject || typeEncoding[0] == FLEXTypeEncodingObjcClass) {
  84. ptrdiff_t offset = ivar_getOffset(ivar);
  85. uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
  86. if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
  87. [instances addObject:[FLEXObjectRef referencing:tryObject ivar:@(ivar_getName(ivar))]];
  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. FLEXInstancesTableViewController *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. {
  105. // These are the types of references that we typically don't care about.
  106. // We want this list of "object-ivar pairs" split into two sections.
  107. BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  108. NSString *row = ref.reference;
  109. return [row isEqualToString:@"__NSObserver object"] ||
  110. [row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
  111. };
  112. /// These are common AutoLayout related references we also rarely care about.
  113. BOOL(^isConstraintRelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  114. static NSSet *ignored = nil;
  115. static dispatch_once_t onceToken;
  116. dispatch_once(&onceToken, ^{
  117. ignored = [NSSet setWithArray:@[
  118. @"NSLayoutConstraint _container",
  119. @"NSContentSizeLayoutConstraint _container",
  120. @"NSAutoresizingMaskLayoutConstraint _container",
  121. @"MASViewConstraint _installedView",
  122. @"MASLayoutConstraint _container",
  123. @"MASViewAttribute _view"
  124. ]];
  125. });
  126. NSString *row = ref.reference;
  127. return ([row hasPrefix:@"NSLayout"] && [row hasSuffix:@" _referenceItem"]) ||
  128. ([row hasPrefix:@"NSIS"] && [row hasSuffix:@" _delegate"]) ||
  129. ([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
  130. [ignored containsObject:row];
  131. };
  132. BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
  133. return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
  134. };
  135. switch (section) {
  136. case 0: return [NSPredicate predicateWithBlock:isEssential];
  137. case 1: return [NSPredicate predicateWithBlock:isConstraintRelated];
  138. case 2: return [NSPredicate predicateWithBlock:isObserver];
  139. default: return nil;
  140. }
  141. }
  142. + (NSArray<NSPredicate *> *)defaultPredicates {
  143. return @[[self defaultPredicateForSection:0],
  144. [self defaultPredicateForSection:1],
  145. [self defaultPredicateForSection:2]];
  146. }
  147. + (NSArray<NSString *> *)defaultSectionTitles {
  148. return @[@"", @"AutoLayout", @"Trivial"];
  149. }
  150. - (void)buildSections
  151. {
  152. NSInteger maxSections = self.maxSections;
  153. NSMutableArray *sections = [NSMutableArray array];
  154. for (NSInteger i = 0; i < maxSections; i++) {
  155. NSPredicate *predicate = self.predicates[i];
  156. [sections addObject:[self.instances filteredArrayUsingPredicate:predicate]];
  157. }
  158. self.sections = sections;
  159. }
  160. - (NSInteger)maxSections {
  161. return self.predicates.count ?: 1;
  162. }
  163. #pragma mark - Table View Data Source
  164. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  165. {
  166. return self.maxSections;
  167. }
  168. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  169. {
  170. return self.sections[section].count;
  171. }
  172. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  173. {
  174. static NSString *CellIdentifier = @"Cell";
  175. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  176. if (!cell) {
  177. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
  178. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  179. UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
  180. cell.textLabel.font = cellFont;
  181. cell.detailTextLabel.font = cellFont;
  182. cell.detailTextLabel.textColor = UIColor.grayColor;
  183. }
  184. FLEXObjectRef *row = self.sections[indexPath.section][indexPath.row];
  185. cell.textLabel.text = row.reference;
  186. cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:row.object];
  187. return cell;
  188. }
  189. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  190. {
  191. if (self.sectionTitles.count) {
  192. // Return nil instead of empty strings
  193. NSString *title = self.sectionTitles[section];
  194. if (title.length) {
  195. return title;
  196. }
  197. }
  198. return nil;
  199. }
  200. #pragma mark - Table View Delegate
  201. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  202. {
  203. id instance = self.instances[indexPath.row].object;
  204. FLEXObjectExplorerViewController *drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:instance];
  205. [self.navigationController pushViewController:drillInViewController animated:YES];
  206. }
  207. @end