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.

233 lines
9.9 KiB

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