// // FLEXObjectListViewController.m // Flipboard // // Created by Ryan Olson on 5/28/14. // Copyright (c) 2020 FLEX Team. All rights reserved. // #import "FLEXObjectListViewController.h" #import "FLEXObjectExplorerFactory.h" #import "FLEXObjectExplorerViewController.h" #import "FLEXMutableListSection.h" #import "FLEXRuntimeUtility.h" #import "FLEXUtility.h" #import "FLEXHeapEnumerator.h" #import "FLEXObjectRef.h" #import "NSString+FLEX.h" #import "NSObject+FLEX_Reflection.h" #import "FLEXTableViewCell.h" #import typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) { FLEXObjectReferenceSectionMain, FLEXObjectReferenceSectionAutoLayout, FLEXObjectReferenceSectionKVO, FLEXObjectReferenceSectionFLEX, FLEXObjectReferenceSectionCount }; @interface FLEXObjectListViewController () @property (nonatomic, readonly, class) NSArray *defaultPredicates; @property (nonatomic, readonly, class) NSArray *defaultSectionTitles; @property (nonatomic, copy) NSArray *sections; @property (nonatomic, copy) NSArray *allSections; @property (nonatomic, readonly, nullable) NSArray *references; @property (nonatomic, readonly) NSArray *predicates; @property (nonatomic, readonly) NSArray *sectionTitles; @end @implementation FLEXObjectListViewController @dynamic sections, allSections; #pragma mark - Reference Grouping + (NSPredicate *)defaultPredicateForSection:(NSInteger)section { // These are the types of references that we typically don't care about. // We want this list of "object-ivar pairs" split into two sections. BOOL(^isKVORelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) { NSString *row = ref.reference; return [row isEqualToString:@"__NSObserver object"] || [row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"]; }; /// These are common AutoLayout related references we also rarely care about. BOOL(^isConstraintRelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) { static NSSet *ignored = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ ignored = [NSSet setWithArray:@[ @"NSLayoutConstraint _container", @"NSContentSizeLayoutConstraint _container", @"NSAutoresizingMaskLayoutConstraint _container", @"MASViewConstraint _installedView", @"MASLayoutConstraint _container", @"MASViewAttribute _view" ]]; }); NSString *row = ref.reference; return ([row hasPrefix:@"NSLayout"] && [row hasSuffix:@" _referenceItem"]) || ([row hasPrefix:@"NSIS"] && [row hasSuffix:@" _delegate"]) || ([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) || [ignored containsObject:row]; }; /// These are FLEX classes and usually you aren't looking for FLEX references inside FLEX itself BOOL(^isFLEXClass)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) { return [ref.reference hasPrefix:@"FLEX"]; }; BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) { return !( isKVORelated(ref, bindings) || isConstraintRelated(ref, bindings) || isFLEXClass(ref, bindings) ); }; switch (section) { case FLEXObjectReferenceSectionMain: return [NSPredicate predicateWithBlock:isEssential]; case FLEXObjectReferenceSectionAutoLayout: return [NSPredicate predicateWithBlock:isConstraintRelated]; case FLEXObjectReferenceSectionKVO: return [NSPredicate predicateWithBlock:isKVORelated]; case FLEXObjectReferenceSectionFLEX: return [NSPredicate predicateWithBlock:isFLEXClass]; default: return nil; } } + (NSArray *)defaultPredicates { return [NSArray flex_forEachUpTo:FLEXObjectReferenceSectionCount map:^id(NSUInteger i) { return [self defaultPredicateForSection:i]; }]; } + (NSArray *)defaultSectionTitles { return @[ @"", @"AutoLayout", @"Key-Value Observing", @"FLEX" ]; } #pragma mark - Initialization - (id)initWithReferences:(nullable NSArray *)references { return [self initWithReferences:references predicates:nil sectionTitles:nil]; } - (id)initWithReferences:(NSArray *)references predicates:(NSArray *)predicates sectionTitles:(NSArray *)sectionTitles { NSParameterAssert(predicates.count == sectionTitles.count); self = [super initWithStyle:UITableViewStylePlain]; if (self) { _references = references; _predicates = predicates; _sectionTitles = sectionTitles; } return self; } + (UIViewController *)instancesOfClassWithName:(NSString *)className retained:(BOOL)retain { NSArray *references = [FLEXHeapEnumerator instancesOfClassWithName:className retained:retain ]; if (references.count == 1) { return [FLEXObjectExplorerFactory explorerViewControllerForObject:references.firstObject.object ]; } FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references]; controller.title = [NSString stringWithFormat:@"%@ (%@)", className, @(references.count)]; return controller; } + (instancetype)subclassesOfClassWithName:(NSString *)className { NSArray *references = [FLEXHeapEnumerator subclassesOfClassWithName:className]; FLEXObjectListViewController *controller = [[self alloc] initWithReferences:references]; controller.title = [NSString stringWithFormat:@"Subclasses of %@ (%@)", className, @(references.count) ]; return controller; } + (instancetype)objectsWithReferencesToObject:(id)object retained:(BOOL)retain { static Class SwiftObjectClass = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ SwiftObjectClass = NSClassFromString(@"SwiftObject"); if (!SwiftObjectClass) { SwiftObjectClass = NSClassFromString(@"Swift._SwiftObject"); } }); NSArray *instances = [FLEXHeapEnumerator objectsWithReferencesToObject:object retained:retain ]; FLEXObjectListViewController *viewController = [[self alloc] initWithReferences:instances predicates:self.defaultPredicates sectionTitles:self.defaultSectionTitles ]; viewController.title = [NSString stringWithFormat:@"Referencing %@ %p", [FLEXRuntimeUtility safeClassNameForObject:object], object ]; return viewController; } #pragma mark - Overrides - (void)viewDidLoad { [super viewDidLoad]; self.showsSearchBar = YES; } - (NSArray *)makeSections { if (self.predicates.count) { return [self buildSections:self.sectionTitles predicates:self.predicates]; } else { return @[[self makeSection:self.references title:nil]]; } } #pragma mark - Private - (NSArray *)buildSections:(NSArray *)titles predicates:(NSArray *)predicates { NSParameterAssert(titles.count == predicates.count); NSParameterAssert(titles); NSParameterAssert(predicates); return [NSArray flex_forEachUpTo:titles.count map:^id(NSUInteger i) { NSArray *rows = [self.references filteredArrayUsingPredicate:predicates[i]]; return [self makeSection:rows title:titles[i]]; }]; } - (FLEXMutableListSection *)makeSection:(NSArray *)rows title:(NSString *)title { FLEXMutableListSection *section = [FLEXMutableListSection list:rows cellConfiguration:^(FLEXTableViewCell *cell, FLEXObjectRef *ref, NSInteger row) { cell.textLabel.text = ref.reference; cell.detailTextLabel.text = ref.summary; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } filterMatcher:^BOOL(NSString *filterText, FLEXObjectRef *ref) { if (ref.summary && [ref.summary localizedCaseInsensitiveContainsString:filterText]) { return YES; } return [ref.reference localizedCaseInsensitiveContainsString:filterText]; } ]; section.selectionHandler = ^(UIViewController *host, FLEXObjectRef *ref) { [host.navigationController pushViewController:[ FLEXObjectExplorerFactory explorerViewControllerForObject:ref.object ] animated:YES]; }; section.customTitle = title; return section; } @end