FLEXLiveObjectsTableViewController.m 9.3 KB

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