FLEXLiveObjectsTableViewController.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. //
  2. // FLEXLiveObjectsTableViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 5/28/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXLiveObjectsTableViewController.h"
  9. #import "FLEXHeapEnumerator.h"
  10. #import "FLEXInstancesTableViewController.h"
  11. #import "FLEXUtility.h"
  12. #import "FLEXScopeCarousel.h"
  13. #import "FLEXTableView.h"
  14. #import <objc/runtime.h>
  15. static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
  16. static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
  17. static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
  18. @interface FLEXLiveObjectsTableViewController ()
  19. @property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceCountsForClassNames;
  20. @property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
  21. @property (nonatomic, readonly) NSArray<NSString *> *allClassNames;
  22. @property (nonatomic) NSArray<NSString *> *filteredClassNames;
  23. @property (nonatomic) NSString *headerTitle;
  24. @end
  25. @implementation FLEXLiveObjectsTableViewController
  26. - (void)loadView
  27. {
  28. self.tableView = [FLEXTableView flexDefaultTableView];
  29. }
  30. - (void)viewDidLoad
  31. {
  32. [super viewDidLoad];
  33. // self.title = @"Live Objects";
  34. self.showsSearchBar = YES;
  35. self.searchBarDebounceInterval = kFLEXDebounceInstant;
  36. self.showsCarousel = YES;
  37. self.carousel.items = @[@"A→Z", @"Count", @"Size"];
  38. self.refreshControl = [UIRefreshControl new];
  39. [self.refreshControl addTarget:self action:@selector(refreshControlDidRefresh:) forControlEvents:UIControlEventValueChanged];
  40. [self reloadTableData];
  41. }
  42. - (NSArray<NSString *> *)allClassNames
  43. {
  44. return self.instanceCountsForClassNames.allKeys;
  45. }
  46. - (void)reloadTableData
  47. {
  48. // Set up a CFMutableDictionary with class pointer keys and NSUInteger values.
  49. // We abuse CFMutableDictionary a little to have primitive keys through judicious casting, but it gets the job done.
  50. // The dictionary is intialized with a 0 count for each class so that it doesn't have to expand during enumeration.
  51. // While it might be a little cleaner to populate an NSMutableDictionary with class name string keys to NSNumber counts,
  52. // we choose the CF/primitives approach because it lets us enumerate the objects in the heap without allocating any memory during enumeration.
  53. // The alternative of creating one NSString/NSNumber per object on the heap ends up polluting the count of live objects quite a bit.
  54. unsigned int classCount = 0;
  55. Class *classes = objc_copyClassList(&classCount);
  56. CFMutableDictionaryRef mutableCountsForClasses = CFDictionaryCreateMutable(NULL, classCount, NULL, NULL);
  57. for (unsigned int i = 0; i < classCount; i++) {
  58. CFDictionarySetValue(mutableCountsForClasses, (__bridge const void *)classes[i], (const void *)0);
  59. }
  60. // Enumerate all objects on the heap to build the counts of instances for each class.
  61. [FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
  62. NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)actualClass);
  63. instanceCount++;
  64. CFDictionarySetValue(mutableCountsForClasses, (__bridge const void *)actualClass, (const void *)instanceCount);
  65. }];
  66. // Convert our CF primitive dictionary into a nicer mapping of class name strings to counts that we will use as the table's model.
  67. NSMutableDictionary<NSString *, NSNumber *> *mutableCountsForClassNames = [NSMutableDictionary dictionary];
  68. NSMutableDictionary<NSString *, NSNumber *> *mutableSizesForClassNames = [NSMutableDictionary dictionary];
  69. for (unsigned int i = 0; i < classCount; i++) {
  70. Class class = classes[i];
  71. NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)(class));
  72. NSString *className = @(class_getName(class));
  73. if (instanceCount > 0) {
  74. [mutableCountsForClassNames setObject:@(instanceCount) forKey:className];
  75. }
  76. [mutableSizesForClassNames setObject:@(class_getInstanceSize(class)) forKey:className];
  77. }
  78. free(classes);
  79. self.instanceCountsForClassNames = mutableCountsForClassNames;
  80. self.instanceSizesForClassNames = mutableSizesForClassNames;
  81. [self updateSearchResults:nil];
  82. }
  83. - (void)refreshControlDidRefresh:(id)sender
  84. {
  85. [self reloadTableData];
  86. [self.refreshControl endRefreshing];
  87. }
  88. - (void)updateHeaderTitle
  89. {
  90. NSUInteger totalCount = 0;
  91. NSUInteger totalSize = 0;
  92. for (NSString *className in self.allClassNames) {
  93. NSUInteger count = self.instanceCountsForClassNames[className].unsignedIntegerValue;
  94. totalCount += count;
  95. totalSize += count * self.instanceSizesForClassNames[className].unsignedIntegerValue;
  96. }
  97. NSUInteger filteredCount = 0;
  98. NSUInteger filteredSize = 0;
  99. for (NSString *className in self.filteredClassNames) {
  100. NSUInteger count = self.instanceCountsForClassNames[className].unsignedIntegerValue;
  101. filteredCount += count;
  102. filteredSize += count * self.instanceSizesForClassNames[className].unsignedIntegerValue;
  103. }
  104. if (filteredCount == totalCount) {
  105. // Unfiltered
  106. self.headerTitle = [NSString
  107. stringWithFormat:@"%@ objects, %@",
  108. @(totalCount), [NSByteCountFormatter
  109. stringFromByteCount:totalSize
  110. countStyle:NSByteCountFormatterCountStyleFile
  111. ]
  112. ];
  113. } else {
  114. self.headerTitle = [NSString
  115. stringWithFormat:@"%@ of %@ objects, %@",
  116. @(filteredCount), @(totalCount), [NSByteCountFormatter
  117. stringFromByteCount:filteredSize
  118. countStyle:NSByteCountFormatterCountStyleFile
  119. ]
  120. ];
  121. }
  122. }
  123. #pragma mark - FLEXGlobalsEntry
  124. + (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
  125. return @"💩 Heap Objects";
  126. }
  127. + (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
  128. FLEXLiveObjectsTableViewController *liveObjectsViewController = [self new];
  129. liveObjectsViewController.title = [self globalsEntryTitle:row];
  130. return liveObjectsViewController;
  131. }
  132. #pragma mark - Search bar
  133. - (void)updateSearchResults:(NSString *)filter
  134. {
  135. NSInteger selectedScope = self.selectedScope;
  136. if (filter.length) {
  137. NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", filter];
  138. self.filteredClassNames = [self.allClassNames filteredArrayUsingPredicate:searchPredicate];
  139. } else {
  140. self.filteredClassNames = self.allClassNames;
  141. }
  142. if (selectedScope == kFLEXLiveObjectsSortAlphabeticallyIndex) {
  143. self.filteredClassNames = [self.filteredClassNames sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  144. } else if (selectedScope == kFLEXLiveObjectsSortByCountIndex) {
  145. self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
  146. NSNumber *count1 = self.instanceCountsForClassNames[className1];
  147. NSNumber *count2 = self.instanceCountsForClassNames[className2];
  148. // Reversed for descending counts.
  149. return [count2 compare:count1];
  150. }];
  151. } else if (selectedScope == kFLEXLiveObjectsSortBySizeIndex) {
  152. self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
  153. NSNumber *count1 = self.instanceCountsForClassNames[className1];
  154. NSNumber *count2 = self.instanceCountsForClassNames[className2];
  155. NSNumber *size1 = self.instanceSizesForClassNames[className1];
  156. NSNumber *size2 = self.instanceSizesForClassNames[className2];
  157. // Reversed for descending sizes.
  158. return [@(count2.integerValue * size2.integerValue) compare:@(count1.integerValue * size1.integerValue)];
  159. }];
  160. }
  161. [self updateHeaderTitle];
  162. [self.tableView reloadData];
  163. }
  164. #pragma mark - Table view data source
  165. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  166. {
  167. return 1;
  168. }
  169. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  170. {
  171. return self.filteredClassNames.count;
  172. }
  173. - (UITableViewCell *)tableView:(__kindof UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  174. {
  175. UITableViewCell *cell = [tableView
  176. dequeueReusableCellWithIdentifier:kFLEXDefaultCell
  177. forIndexPath:indexPath
  178. ];
  179. NSString *className = self.filteredClassNames[indexPath.row];
  180. NSNumber *count = self.instanceCountsForClassNames[className];
  181. NSNumber *size = self.instanceSizesForClassNames[className];
  182. unsigned long totalSize = count.unsignedIntegerValue * size.unsignedIntegerValue;
  183. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  184. cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld, %@)",
  185. className, (long)[count integerValue],
  186. [NSByteCountFormatter
  187. stringFromByteCount:totalSize
  188. countStyle:NSByteCountFormatterCountStyleFile
  189. ]
  190. ];
  191. return cell;
  192. }
  193. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  194. {
  195. return self.headerTitle;
  196. }
  197. #pragma mark - Table view delegate
  198. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  199. {
  200. NSString *className = self.filteredClassNames[indexPath.row];
  201. FLEXInstancesTableViewController *instancesViewController = [FLEXInstancesTableViewController instancesTableViewControllerForClassName:className];
  202. [self.navigationController pushViewController:instancesViewController animated:YES];
  203. }
  204. @end