FLEXLiveObjectsTableViewController.m 8.7 KB

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