FLEXInstancesViewController.m 10.0 KB

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