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.

245 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. 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. {
  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. const char *typeEncoding = ivar_getTypeEncoding(ivar);
  82. if (typeEncoding[0] == @encode(id)[0] || typeEncoding[0] == @encode(Class)[0]) {
  83. ptrdiff_t offset = ivar_getOffset(ivar);
  84. uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
  85. if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
  86. [instances addObject:[FLEXObjectRef referencing:tryObject ivar:@(ivar_getName(ivar))]];
  87. return;
  88. }
  89. }
  90. }
  91. tryClass = class_getSuperclass(tryClass);
  92. }
  93. }];
  94. NSArray<NSPredicate *> *predicates = [self defaultPredicates];
  95. NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
  96. FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:instances
  97. predicates:predicates
  98. sectionTitles:sectionTitles];
  99. viewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
  100. return viewController;
  101. }
  102. + (NSPredicate *)defaultPredicateForSection:(NSInteger)section
  103. {
  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. {
  151. NSInteger maxSections = self.maxSections;
  152. NSMutableArray *sections = [NSMutableArray array];
  153. for (NSInteger i = 0; i < maxSections; i++) {
  154. NSPredicate *predicate = self.predicates[i];
  155. [sections addObject:[self.instances filteredArrayUsingPredicate:predicate]];
  156. }
  157. self.sections = sections;
  158. }
  159. - (NSInteger)maxSections {
  160. return self.predicates.count ?: 1;
  161. }
  162. #pragma mark - Table View Data Source
  163. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  164. {
  165. return self.maxSections;
  166. }
  167. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  168. {
  169. return self.sections[section].count;
  170. }
  171. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  172. {
  173. static NSString *CellIdentifier = @"Cell";
  174. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  175. if (!cell) {
  176. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
  177. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  178. UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
  179. cell.textLabel.font = cellFont;
  180. cell.detailTextLabel.font = cellFont;
  181. cell.detailTextLabel.textColor = [UIColor grayColor];
  182. }
  183. FLEXObjectRef *row = self.sections[indexPath.section][indexPath.row];
  184. cell.textLabel.text = row.reference;
  185. cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:row.object];
  186. return cell;
  187. }
  188. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  189. {
  190. if (self.sectionTitles.count) {
  191. // Return nil instead of empty strings
  192. NSString *title = self.sectionTitles[section];
  193. if (title.length) {
  194. return title;
  195. }
  196. }
  197. return nil;
  198. }
  199. #pragma mark - Table View Delegate
  200. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  201. {
  202. id instance = self.instances[indexPath.row].object;
  203. FLEXObjectExplorerViewController *drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:instance];
  204. [self.navigationController pushViewController:drillInViewController animated:YES];
  205. }
  206. @end