mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:04 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			237 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  FLEXLiveObjectsController.m
 | |
| //  Flipboard
 | |
| //
 | |
| //  Created by Ryan Olson on 5/28/14.
 | |
| //  Copyright (c) 2020 FLEX Team. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "FLEXLiveObjectsController.h"
 | |
| #import "FLEXHeapEnumerator.h"
 | |
| #import "FLEXObjectListViewController.h"
 | |
| #import "FLEXUtility.h"
 | |
| #import "FLEXScopeCarousel.h"
 | |
| #import "FLEXTableView.h"
 | |
| #import <objc/runtime.h>
 | |
| 
 | |
| static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
 | |
| static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
 | |
| static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
 | |
| 
 | |
| @interface FLEXLiveObjectsController ()
 | |
| 
 | |
| @property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceCountsForClassNames;
 | |
| @property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
 | |
| @property (nonatomic, readonly) NSArray<NSString *> *allClassNames;
 | |
| @property (nonatomic) NSArray<NSString *> *filteredClassNames;
 | |
| @property (nonatomic) NSString *headerTitle;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation FLEXLiveObjectsController
 | |
| 
 | |
| - (void)viewDidLoad {
 | |
|     [super viewDidLoad];
 | |
| 
 | |
|     self.showsSearchBar = YES;
 | |
|     self.showSearchBarInitially = YES;
 | |
|     self.activatesSearchBarAutomatically = YES;
 | |
|     self.searchBarDebounceInterval = kFLEXDebounceInstant;
 | |
|     self.showsCarousel = YES;
 | |
|     self.carousel.items = @[@"A→Z", @"Count", @"Size"];
 | |
|     
 | |
|     self.refreshControl = [UIRefreshControl new];
 | |
|     [self.refreshControl addTarget:self action:@selector(refreshControlDidRefresh:) forControlEvents:UIControlEventValueChanged];
 | |
|     
 | |
|     [self reloadTableData];
 | |
| }
 | |
| 
 | |
| - (NSArray<NSString *> *)allClassNames {
 | |
|     return self.instanceCountsForClassNames.allKeys;
 | |
| }
 | |
| 
 | |
| - (void)reloadTableData {
 | |
|     // Set up a CFMutableDictionary with class pointer keys and NSUInteger values.
 | |
|     // We abuse CFMutableDictionary a little to have primitive keys through judicious casting, but it gets the job done.
 | |
|     // The dictionary is intialized with a 0 count for each class so that it doesn't have to expand during enumeration.
 | |
|     // While it might be a little cleaner to populate an NSMutableDictionary with class name string keys to NSNumber counts,
 | |
|     // we choose the CF/primitives approach because it lets us enumerate the objects in the heap without allocating any memory during enumeration.
 | |
|     // The alternative of creating one NSString/NSNumber per object on the heap ends up polluting the count of live objects quite a bit.
 | |
|     unsigned int classCount = 0;
 | |
|     Class *classes = objc_copyClassList(&classCount);
 | |
|     CFMutableDictionaryRef mutableCountsForClasses = CFDictionaryCreateMutable(NULL, classCount, NULL, NULL);
 | |
|     for (unsigned int i = 0; i < classCount; i++) {
 | |
|         CFDictionarySetValue(mutableCountsForClasses, (__bridge const void *)classes[i], (const void *)0);
 | |
|     }
 | |
|     
 | |
|     // Enumerate all objects on the heap to build the counts of instances for each class.
 | |
|     [FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
 | |
|         NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)actualClass);
 | |
|         instanceCount++;
 | |
|         CFDictionarySetValue(mutableCountsForClasses, (__bridge const void *)actualClass, (const void *)instanceCount);
 | |
|     }];
 | |
|     
 | |
|     // Convert our CF primitive dictionary into a nicer mapping of class name strings to counts that we will use as the table's model.
 | |
|     NSMutableDictionary<NSString *, NSNumber *> *mutableCountsForClassNames = [NSMutableDictionary new];
 | |
|     NSMutableDictionary<NSString *, NSNumber *> *mutableSizesForClassNames = [NSMutableDictionary new];
 | |
|     for (unsigned int i = 0; i < classCount; i++) {
 | |
|         Class class = classes[i];
 | |
|         NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)(class));
 | |
|         NSString *className = @(class_getName(class));
 | |
|         if (instanceCount > 0) {
 | |
|             [mutableCountsForClassNames setObject:@(instanceCount) forKey:className];
 | |
|         }
 | |
|         [mutableSizesForClassNames setObject:@(class_getInstanceSize(class)) forKey:className];
 | |
|     }
 | |
|     free(classes);
 | |
|     
 | |
|     self.instanceCountsForClassNames = mutableCountsForClassNames;
 | |
|     self.instanceSizesForClassNames = mutableSizesForClassNames;
 | |
|     
 | |
|     [self updateSearchResults:nil];
 | |
| }
 | |
| 
 | |
| - (void)refreshControlDidRefresh:(id)sender {
 | |
|     [self reloadTableData];
 | |
|     [self.refreshControl endRefreshing];
 | |
| }
 | |
| 
 | |
| - (void)updateHeaderTitle {
 | |
|     NSUInteger totalCount = 0;
 | |
|     NSUInteger totalSize = 0;
 | |
|     for (NSString *className in self.allClassNames) {
 | |
|         NSUInteger count = self.instanceCountsForClassNames[className].unsignedIntegerValue;
 | |
|         totalCount += count;
 | |
|         totalSize += count * self.instanceSizesForClassNames[className].unsignedIntegerValue;
 | |
|     }
 | |
| 
 | |
|     NSUInteger filteredCount = 0;
 | |
|     NSUInteger filteredSize = 0;
 | |
|     for (NSString *className in self.filteredClassNames) {
 | |
|         NSUInteger count = self.instanceCountsForClassNames[className].unsignedIntegerValue;
 | |
|         filteredCount += count;
 | |
|         filteredSize += count * self.instanceSizesForClassNames[className].unsignedIntegerValue;
 | |
|     }
 | |
|     
 | |
|     if (filteredCount == totalCount) {
 | |
|         // Unfiltered
 | |
|         self.headerTitle = [NSString
 | |
|             stringWithFormat:@"%@ objects, %@",
 | |
|             @(totalCount), [NSByteCountFormatter
 | |
|                 stringFromByteCount:totalSize
 | |
|                 countStyle:NSByteCountFormatterCountStyleFile
 | |
|             ]
 | |
|         ];
 | |
|     } else {
 | |
|         self.headerTitle = [NSString
 | |
|             stringWithFormat:@"%@ of %@ objects, %@",
 | |
|             @(filteredCount), @(totalCount), [NSByteCountFormatter
 | |
|                 stringFromByteCount:filteredSize
 | |
|                 countStyle:NSByteCountFormatterCountStyleFile
 | |
|             ]
 | |
|         ];
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - FLEXGlobalsEntry
 | |
| 
 | |
| + (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
 | |
|     return @"💩  Heap Objects";
 | |
| }
 | |
| 
 | |
| + (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
 | |
|     FLEXLiveObjectsController *liveObjectsViewController = [self new];
 | |
|     liveObjectsViewController.title = [self globalsEntryTitle:row];
 | |
| 
 | |
|     return liveObjectsViewController;
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - Search bar
 | |
| 
 | |
| - (void)updateSearchResults:(NSString *)filter {
 | |
|     NSInteger selectedScope = self.selectedScope;
 | |
|     
 | |
|     if (filter.length) {
 | |
|         NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", filter];
 | |
|         self.filteredClassNames = [self.allClassNames filteredArrayUsingPredicate:searchPredicate];
 | |
|     } else {
 | |
|         self.filteredClassNames = self.allClassNames;
 | |
|     }
 | |
|     
 | |
|     if (selectedScope == kFLEXLiveObjectsSortAlphabeticallyIndex) {
 | |
|         self.filteredClassNames = [self.filteredClassNames sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
 | |
|     } else if (selectedScope == kFLEXLiveObjectsSortByCountIndex) {
 | |
|         self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
 | |
|             NSNumber *count1 = self.instanceCountsForClassNames[className1];
 | |
|             NSNumber *count2 = self.instanceCountsForClassNames[className2];
 | |
|             // Reversed for descending counts.
 | |
|             return [count2 compare:count1];
 | |
|         }];
 | |
|     } else if (selectedScope == kFLEXLiveObjectsSortBySizeIndex) {
 | |
|         self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
 | |
|             NSNumber *count1 = self.instanceCountsForClassNames[className1];
 | |
|             NSNumber *count2 = self.instanceCountsForClassNames[className2];
 | |
|             NSNumber *size1 = self.instanceSizesForClassNames[className1];
 | |
|             NSNumber *size2 = self.instanceSizesForClassNames[className2];
 | |
|             // Reversed for descending sizes.
 | |
|             return [@(count2.integerValue * size2.integerValue) compare:@(count1.integerValue * size1.integerValue)];
 | |
|         }];
 | |
|     }
 | |
|     
 | |
|     [self updateHeaderTitle];
 | |
|     [self.tableView reloadData];
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - Table view data source
 | |
| 
 | |
| - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 | |
|     return self.filteredClassNames.count;
 | |
| }
 | |
| 
 | |
| - (UITableViewCell *)tableView:(__kindof UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 | |
|     UITableViewCell *cell = [tableView
 | |
|         dequeueReusableCellWithIdentifier:kFLEXDefaultCell
 | |
|         forIndexPath:indexPath
 | |
|     ];
 | |
| 
 | |
|     NSString *className = self.filteredClassNames[indexPath.row];
 | |
|     NSNumber *count = self.instanceCountsForClassNames[className];
 | |
|     NSNumber *size = self.instanceSizesForClassNames[className];
 | |
|     unsigned long totalSize = count.unsignedIntegerValue * size.unsignedIntegerValue;
 | |
|     cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
 | |
|     cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld, %@)",
 | |
|         className, (long)[count integerValue],
 | |
|         [NSByteCountFormatter
 | |
|             stringFromByteCount:totalSize
 | |
|             countStyle:NSByteCountFormatterCountStyleFile
 | |
|         ]
 | |
|     ];
 | |
|     
 | |
|     return cell;
 | |
| }
 | |
| 
 | |
| - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
 | |
|     return self.headerTitle;
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - Table view delegate
 | |
| 
 | |
| - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 | |
|     NSString *className = self.filteredClassNames[indexPath.row];
 | |
|     UIViewController *instances = [FLEXObjectListViewController
 | |
|         instancesOfClassWithName:className
 | |
|         retained:YES
 | |
|     ];
 | |
|     [self.navigationController pushViewController:instances animated:YES];
 | |
| }
 | |
| 
 | |
| @end
 | 
