FLEXObjectExplorerViewController.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. //
  2. // FLEXObjectExplorerViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 2014-05-03.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXObjectExplorerViewController.h"
  9. #import "FLEXUtility.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "FLEXDescriptionTableViewCell.h"
  12. #import "FLEXObjectExplorerFactory.h"
  13. #import "FLEXPropertyEditorViewController.h"
  14. #import "FLEXIvarEditorViewController.h"
  15. #import "FLEXMethodCallingViewController.h"
  16. #import "FLEXInstancesTableViewController.h"
  17. #import <objc/runtime.h>
  18. // Convenience boxes to keep runtime properties, ivars, and methods in foundation collections.
  19. @interface FLEXPropertyBox : NSObject
  20. @property (nonatomic, assign) objc_property_t property;
  21. @end
  22. @implementation FLEXPropertyBox
  23. @end
  24. @interface FLEXIvarBox : NSObject
  25. @property (nonatomic, assign) Ivar ivar;
  26. @end
  27. @implementation FLEXIvarBox
  28. @end
  29. @interface FLEXMethodBox : NSObject
  30. @property (nonatomic, assign) Method method;
  31. @end
  32. @implementation FLEXMethodBox
  33. @end
  34. static const NSInteger kFLEXObjectExplorerScopeNoInheritanceIndex = 0;
  35. static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
  36. @interface FLEXObjectExplorerViewController () <UISearchBarDelegate>
  37. @property (nonatomic, strong) NSArray *properties;
  38. @property (nonatomic, strong) NSArray *inheritedProperties;
  39. @property (nonatomic, strong) NSArray *filteredProperties;
  40. @property (nonatomic, strong) NSArray *ivars;
  41. @property (nonatomic, strong) NSArray *inheritedIvars;
  42. @property (nonatomic, strong) NSArray *filteredIvars;
  43. @property (nonatomic, strong) NSArray *methods;
  44. @property (nonatomic, strong) NSArray *inheritedMethods;
  45. @property (nonatomic, strong) NSArray *filteredMethods;
  46. @property (nonatomic, strong) NSArray *classMethods;
  47. @property (nonatomic, strong) NSArray *inheritedClassMethods;
  48. @property (nonatomic, strong) NSArray *filteredClassMethods;
  49. @property (nonatomic, strong) NSArray *superclasses;
  50. @property (nonatomic, strong) NSArray *filteredSuperclasses;
  51. @property (nonatomic, strong) NSArray *cachedCustomSectionRowCookies;
  52. @property (nonatomic, strong) NSIndexSet *customSectionVisibleIndexes;
  53. @property (nonatomic, strong) UISearchBar *searchBar;
  54. @property (nonatomic, strong) NSString *filterText;
  55. @property (nonatomic, assign) BOOL includeInheritance;
  56. @end
  57. @implementation FLEXObjectExplorerViewController
  58. - (id)initWithStyle:(UITableViewStyle)style
  59. {
  60. // Force grouped style
  61. return [super initWithStyle:UITableViewStyleGrouped];
  62. }
  63. - (void)viewDidLoad
  64. {
  65. [super viewDidLoad];
  66. self.searchBar = [[UISearchBar alloc] init];
  67. self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
  68. self.searchBar.delegate = self;
  69. self.searchBar.showsScopeBar = YES;
  70. self.searchBar.scopeButtonTitles = @[@"No Inheritance", @"Include Inheritance"];
  71. [self.searchBar sizeToFit];
  72. self.tableView.tableHeaderView = self.searchBar;
  73. self.refreshControl = [[UIRefreshControl alloc] init];
  74. [self.refreshControl addTarget:self action:@selector(refreshControlDidRefresh:) forControlEvents:UIControlEventValueChanged];
  75. }
  76. - (void)viewWillAppear:(BOOL)animated
  77. {
  78. [super viewWillAppear:animated];
  79. // Reload the entire table view rather than just the visible cells because the filtered rows
  80. // may have changed (i.e. a change in the description row that causes it to get filtered out).
  81. [self updateTableData];
  82. }
  83. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  84. {
  85. [self.searchBar endEditing:YES];
  86. }
  87. - (void)refreshControlDidRefresh:(id)sender
  88. {
  89. [self updateTableData];
  90. [self.refreshControl endRefreshing];
  91. }
  92. #pragma mark - Search
  93. - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
  94. {
  95. self.filterText = searchText;
  96. }
  97. - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
  98. {
  99. if (selectedScope == kFLEXObjectExplorerScopeIncludeInheritanceIndex) {
  100. self.includeInheritance = YES;
  101. } else if (selectedScope == kFLEXObjectExplorerScopeNoInheritanceIndex) {
  102. self.includeInheritance = NO;
  103. }
  104. }
  105. #pragma mark - Setter overrides
  106. - (void)setObject:(id)object
  107. {
  108. _object = object;
  109. // Use [object class] here rather than object_getClass because we don't want to show the KVO prefix for observed objects.
  110. self.title = [[object class] description];
  111. [self updateTableData];
  112. }
  113. - (void)setIncludeInheritance:(BOOL)includeInheritance
  114. {
  115. if (_includeInheritance != includeInheritance) {
  116. _includeInheritance = includeInheritance;
  117. [self updateDisplayedData];
  118. }
  119. }
  120. - (void)setFilterText:(NSString *)filterText
  121. {
  122. if (_filterText != filterText || ![_filterText isEqual:filterText]) {
  123. _filterText = filterText;
  124. [self updateDisplayedData];
  125. }
  126. }
  127. #pragma mark - Reloading
  128. - (void)updateTableData
  129. {
  130. [self updateCustomData];
  131. [self updateProperties];
  132. [self updateIvars];
  133. [self updateMethods];
  134. [self updateClassMethods];
  135. [self updateSuperclasses];
  136. [self updateDisplayedData];
  137. }
  138. - (void)updateDisplayedData
  139. {
  140. [self updateFilteredCustomData];
  141. [self updateFilteredProperties];
  142. [self updateFilteredIvars];
  143. [self updateFilteredMethods];
  144. [self updateFilteredClassMethods];
  145. [self updateFilteredSuperclasses];
  146. if (self.isViewLoaded) {
  147. [self.tableView reloadData];
  148. }
  149. }
  150. - (BOOL)shouldShowDescription
  151. {
  152. BOOL showDescription = YES;
  153. // Not if it's empty or nil.
  154. NSString *descripition = [FLEXUtility safeDescriptionForObject:self.object];
  155. if (showDescription) {
  156. showDescription = [descripition length] > 0;
  157. }
  158. // Not if we have filter text that doesn't match the desctiption.
  159. if (showDescription && [self.filterText length] > 0) {
  160. showDescription = [descripition rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
  161. }
  162. return showDescription;
  163. }
  164. #pragma mark - Properties
  165. - (void)updateProperties
  166. {
  167. Class class = [self.object class];
  168. self.properties = [[self class] propertiesForClass:class];
  169. self.inheritedProperties = [[self class] inheritedPropertiesForClass:class];
  170. }
  171. + (NSArray *)propertiesForClass:(Class)class
  172. {
  173. NSMutableArray *boxedProperties = [NSMutableArray array];
  174. unsigned int propertyCount = 0;
  175. objc_property_t *propertyList = class_copyPropertyList(class, &propertyCount);
  176. if (propertyList) {
  177. for (unsigned int i = 0; i < propertyCount; i++) {
  178. FLEXPropertyBox *propertyBox = [[FLEXPropertyBox alloc] init];
  179. propertyBox.property = propertyList[i];
  180. [boxedProperties addObject:propertyBox];
  181. }
  182. free(propertyList);
  183. }
  184. return boxedProperties;
  185. }
  186. + (NSArray *)inheritedPropertiesForClass:(Class)class
  187. {
  188. NSMutableArray *inheritedProperties = [NSMutableArray array];
  189. while ((class = [class superclass])) {
  190. [inheritedProperties addObjectsFromArray:[self propertiesForClass:class]];
  191. }
  192. return inheritedProperties;
  193. }
  194. - (void)updateFilteredProperties
  195. {
  196. NSArray *candidateProperties = self.properties;
  197. if (self.includeInheritance) {
  198. candidateProperties = [candidateProperties arrayByAddingObjectsFromArray:self.inheritedProperties];
  199. }
  200. NSArray *unsortedFilteredProperties = nil;
  201. if ([self.filterText length] > 0) {
  202. NSMutableArray *mutableUnsortedFilteredProperties = [NSMutableArray array];
  203. for (FLEXPropertyBox *propertyBox in candidateProperties) {
  204. NSString *prettyName = [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  205. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  206. [mutableUnsortedFilteredProperties addObject:propertyBox];
  207. }
  208. }
  209. unsortedFilteredProperties = mutableUnsortedFilteredProperties;
  210. } else {
  211. unsortedFilteredProperties = candidateProperties;
  212. }
  213. self.filteredProperties = [unsortedFilteredProperties sortedArrayUsingComparator:^NSComparisonResult(FLEXPropertyBox *propertyBox1, FLEXPropertyBox *propertyBox2) {
  214. NSString *name1 = [NSString stringWithUTF8String:property_getName(propertyBox1.property)];
  215. NSString *name2 = [NSString stringWithUTF8String:property_getName(propertyBox2.property)];
  216. return [name1 caseInsensitiveCompare:name2];
  217. }];
  218. }
  219. - (NSString *)titleForPropertyAtIndex:(NSInteger)index
  220. {
  221. FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:index];
  222. return [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  223. }
  224. - (id)valueForPropertyAtIndex:(NSInteger)index
  225. {
  226. id value = nil;
  227. if ([self canHaveInstanceState]) {
  228. FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:index];
  229. value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
  230. }
  231. return value;
  232. }
  233. #pragma mark - Ivars
  234. - (void)updateIvars
  235. {
  236. Class class = [self.object class];
  237. self.ivars = [[self class] ivarsForClass:class];
  238. self.inheritedIvars = [[self class] inheritedIvarsForClass:class];
  239. }
  240. + (NSArray *)ivarsForClass:(Class)class
  241. {
  242. NSMutableArray *boxedIvars = [NSMutableArray array];
  243. unsigned int ivarCount = 0;
  244. Ivar *ivarList = class_copyIvarList(class, &ivarCount);
  245. if (ivarList) {
  246. for (unsigned int i = 0; i < ivarCount; i++) {
  247. FLEXIvarBox *ivarBox = [[FLEXIvarBox alloc] init];
  248. ivarBox.ivar = ivarList[i];
  249. [boxedIvars addObject:ivarBox];
  250. }
  251. free(ivarList);
  252. }
  253. return boxedIvars;
  254. }
  255. + (NSArray *)inheritedIvarsForClass:(Class)class
  256. {
  257. NSMutableArray *inheritedIvars = [NSMutableArray array];
  258. while ((class = [class superclass])) {
  259. [inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
  260. }
  261. return inheritedIvars;
  262. }
  263. - (void)updateFilteredIvars
  264. {
  265. NSArray *candidateIvars = self.ivars;
  266. if (self.includeInheritance) {
  267. candidateIvars = [candidateIvars arrayByAddingObjectsFromArray:self.inheritedIvars];
  268. }
  269. NSArray *unsortedFilteredIvars = nil;
  270. if ([self.filterText length] > 0) {
  271. NSMutableArray *mutableUnsortedFilteredIvars = [NSMutableArray array];
  272. for (FLEXIvarBox *ivarBox in candidateIvars) {
  273. NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  274. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  275. [mutableUnsortedFilteredIvars addObject:ivarBox];
  276. }
  277. }
  278. unsortedFilteredIvars = mutableUnsortedFilteredIvars;
  279. } else {
  280. unsortedFilteredIvars = candidateIvars;
  281. }
  282. self.filteredIvars = [unsortedFilteredIvars sortedArrayUsingComparator:^NSComparisonResult(FLEXIvarBox *ivarBox1, FLEXIvarBox *ivarBox2) {
  283. NSString *name1 = [NSString stringWithUTF8String:ivar_getName(ivarBox1.ivar)];
  284. NSString *name2 = [NSString stringWithUTF8String:ivar_getName(ivarBox2.ivar)];
  285. return [name1 caseInsensitiveCompare:name2];
  286. }];
  287. }
  288. - (NSString *)titleForIvarAtIndex:(NSInteger)index
  289. {
  290. FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:index];
  291. return [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  292. }
  293. - (id)valueForIvarAtIndex:(NSInteger)index
  294. {
  295. id value = nil;
  296. if ([self canHaveInstanceState]) {
  297. FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:index];
  298. value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
  299. }
  300. return value;
  301. }
  302. #pragma mark - Methods
  303. - (void)updateMethods
  304. {
  305. Class class = [self.object class];
  306. self.methods = [[self class] methodsForClass:class];
  307. self.inheritedMethods = [[self class] inheritedMethodsForClass:class];
  308. }
  309. - (void)updateFilteredMethods
  310. {
  311. self.filteredMethods = [self filteredMethodsFromMethods:self.methods inheritedMethods:self.inheritedMethods areClassMethods:NO];
  312. }
  313. - (void)updateClassMethods
  314. {
  315. const char *className = [NSStringFromClass([self.object class]) UTF8String];
  316. Class metaClass = objc_getMetaClass(className);
  317. self.classMethods = [[self class] methodsForClass:metaClass];
  318. self.inheritedClassMethods = [[self class] inheritedMethodsForClass:metaClass];
  319. }
  320. - (void)updateFilteredClassMethods
  321. {
  322. self.filteredClassMethods = [self filteredMethodsFromMethods:self.classMethods inheritedMethods:self.inheritedClassMethods areClassMethods:YES];
  323. }
  324. + (NSArray *)methodsForClass:(Class)class
  325. {
  326. NSMutableArray *boxedMethods = [NSMutableArray array];
  327. unsigned int methodCount = 0;
  328. Method *methodList = class_copyMethodList(class, &methodCount);
  329. if (methodList) {
  330. for (unsigned int i = 0; i < methodCount; i++) {
  331. FLEXMethodBox *methodBox = [[FLEXMethodBox alloc] init];
  332. methodBox.method = methodList[i];
  333. [boxedMethods addObject:methodBox];
  334. }
  335. free(methodList);
  336. }
  337. return boxedMethods;
  338. }
  339. + (NSArray *)inheritedMethodsForClass:(Class)class
  340. {
  341. NSMutableArray *inheritedMethods = [NSMutableArray array];
  342. while ((class = [class superclass])) {
  343. [inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
  344. }
  345. return inheritedMethods;
  346. }
  347. - (NSArray *)filteredMethodsFromMethods:(NSArray *)methods inheritedMethods:(NSArray *)inheritedMethods areClassMethods:(BOOL)areClassMethods
  348. {
  349. NSArray *candidateMethods = methods;
  350. if (self.includeInheritance) {
  351. candidateMethods = [candidateMethods arrayByAddingObjectsFromArray:inheritedMethods];
  352. }
  353. NSArray *unsortedFilteredMethods = nil;
  354. if ([self.filterText length] > 0) {
  355. NSMutableArray *mutableUnsortedFilteredMethods = [NSMutableArray array];
  356. for (FLEXMethodBox *methodBox in candidateMethods) {
  357. NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
  358. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  359. [mutableUnsortedFilteredMethods addObject:methodBox];
  360. }
  361. }
  362. unsortedFilteredMethods = mutableUnsortedFilteredMethods;
  363. } else {
  364. unsortedFilteredMethods = candidateMethods;
  365. }
  366. NSArray *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
  367. NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
  368. NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
  369. return [name1 caseInsensitiveCompare:name2];
  370. }];
  371. return sortedFilteredMethods;
  372. }
  373. - (NSString *)titleForMethodAtIndex:(NSInteger)index
  374. {
  375. FLEXMethodBox *methodBox = [self.filteredMethods objectAtIndex:index];
  376. return [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:NO];
  377. }
  378. - (NSString *)titleForClassMethodAtIndex:(NSInteger)index
  379. {
  380. FLEXMethodBox *classMethodBox = [self.filteredClassMethods objectAtIndex:index];
  381. return [FLEXRuntimeUtility prettyNameForMethod:classMethodBox.method isClassMethod:YES];
  382. }
  383. #pragma mark - Superclasses
  384. + (NSArray *)superclassesForClass:(Class)class
  385. {
  386. NSMutableArray *superClasses = [NSMutableArray array];
  387. while ((class = [class superclass])) {
  388. [superClasses addObject:class];
  389. }
  390. return superClasses;
  391. }
  392. - (void)updateSuperclasses
  393. {
  394. self.superclasses = [[self class] superclassesForClass:[self.object class]];
  395. }
  396. - (void)updateFilteredSuperclasses
  397. {
  398. if ([self.filterText length] > 0) {
  399. NSMutableArray *filteredSuperclasses = [NSMutableArray array];
  400. for (Class superclass in self.superclasses) {
  401. if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
  402. [filteredSuperclasses addObject:superclass];
  403. }
  404. }
  405. self.filteredSuperclasses = filteredSuperclasses;
  406. } else {
  407. self.filteredSuperclasses = self.superclasses;
  408. }
  409. }
  410. #pragma mark - Table View Data Helpers
  411. - (NSArray *)possibleExplorerSections
  412. {
  413. static NSArray *possibleSections = nil;
  414. static dispatch_once_t onceToken;
  415. dispatch_once(&onceToken, ^{
  416. possibleSections = @[@(FLEXObjectExplorerSectionDescription),
  417. @(FLEXObjectExplorerSectionCustom),
  418. @(FLEXObjectExplorerSectionProperties),
  419. @(FLEXObjectExplorerSectionIvars),
  420. @(FLEXObjectExplorerSectionMethods),
  421. @(FLEXObjectExplorerSectionClassMethods),
  422. @(FLEXObjectExplorerSectionSuperclasses),
  423. @(FLEXObjectExplorerSectionReferencingInstances)];
  424. });
  425. return possibleSections;
  426. }
  427. - (NSArray *)visibleExplorerSections
  428. {
  429. NSMutableArray *visibleSections = [NSMutableArray array];
  430. for (NSNumber *possibleSection in [self possibleExplorerSections]) {
  431. FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
  432. if ([self numberOfRowsForExplorerSection:explorerSection] > 0) {
  433. [visibleSections addObject:possibleSection];
  434. }
  435. }
  436. return visibleSections;
  437. }
  438. - (NSString *)sectionTitleWithBaseName:(NSString *)baseName totalCount:(NSUInteger)totalCount filteredCount:(NSUInteger)filteredCount
  439. {
  440. NSString *sectionTitle = nil;
  441. if (totalCount == filteredCount) {
  442. sectionTitle = [baseName stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
  443. } else {
  444. sectionTitle = [baseName stringByAppendingFormat:@" (%lu of %lu)", (unsigned long)filteredCount, (unsigned long)totalCount];
  445. }
  446. return sectionTitle;
  447. }
  448. - (FLEXObjectExplorerSection)explorerSectionAtIndex:(NSInteger)sectionIndex
  449. {
  450. return [[[self visibleExplorerSections] objectAtIndex:sectionIndex] unsignedIntegerValue];
  451. }
  452. - (NSInteger)numberOfRowsForExplorerSection:(FLEXObjectExplorerSection)section
  453. {
  454. NSInteger numberOfRows = 0;
  455. switch (section) {
  456. case FLEXObjectExplorerSectionDescription:
  457. numberOfRows = [self shouldShowDescription] ? 1 : 0;
  458. break;
  459. case FLEXObjectExplorerSectionCustom:
  460. numberOfRows = [self.customSectionVisibleIndexes count];
  461. break;
  462. case FLEXObjectExplorerSectionProperties:
  463. numberOfRows = [self.filteredProperties count];
  464. break;
  465. case FLEXObjectExplorerSectionIvars:
  466. numberOfRows = [self.filteredIvars count];
  467. break;
  468. case FLEXObjectExplorerSectionMethods:
  469. numberOfRows = [self.filteredMethods count];
  470. break;
  471. case FLEXObjectExplorerSectionClassMethods:
  472. numberOfRows = [self.filteredClassMethods count];
  473. break;
  474. case FLEXObjectExplorerSectionSuperclasses:
  475. numberOfRows = [self.filteredSuperclasses count];
  476. break;
  477. case FLEXObjectExplorerSectionReferencingInstances:
  478. // Hide this section if there is fliter text since there's nothing searchable (only 1 row, always the same).
  479. numberOfRows = [self.filterText length] == 0 ? 1 : 0;
  480. break;
  481. }
  482. return numberOfRows;
  483. }
  484. - (NSString *)titleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  485. {
  486. NSString *title = nil;
  487. switch (section) {
  488. case FLEXObjectExplorerSectionDescription:
  489. title = [FLEXUtility safeDescriptionForObject:self.object];
  490. break;
  491. case FLEXObjectExplorerSectionCustom:
  492. title = [self customSectionTitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  493. break;
  494. case FLEXObjectExplorerSectionProperties:
  495. title = [self titleForPropertyAtIndex:row];
  496. break;
  497. case FLEXObjectExplorerSectionIvars:
  498. title = [self titleForIvarAtIndex:row];
  499. break;
  500. case FLEXObjectExplorerSectionMethods:
  501. title = [self titleForMethodAtIndex:row];
  502. break;
  503. case FLEXObjectExplorerSectionClassMethods:
  504. title = [self titleForClassMethodAtIndex:row];
  505. break;
  506. case FLEXObjectExplorerSectionSuperclasses:
  507. title = NSStringFromClass([self.filteredSuperclasses objectAtIndex:row]);
  508. break;
  509. case FLEXObjectExplorerSectionReferencingInstances:
  510. title = @"Other objects with ivars referencing this object";
  511. break;
  512. }
  513. return title;
  514. }
  515. - (NSString *)subtitleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  516. {
  517. NSString *subtitle = nil;
  518. switch (section) {
  519. case FLEXObjectExplorerSectionDescription:
  520. break;
  521. case FLEXObjectExplorerSectionCustom:
  522. subtitle = [self customSectionSubtitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  523. break;
  524. case FLEXObjectExplorerSectionProperties:
  525. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForPropertyAtIndex:row]] : nil;
  526. break;
  527. case FLEXObjectExplorerSectionIvars:
  528. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForIvarAtIndex:row]] : nil;
  529. break;
  530. case FLEXObjectExplorerSectionMethods:
  531. break;
  532. case FLEXObjectExplorerSectionClassMethods:
  533. break;
  534. case FLEXObjectExplorerSectionSuperclasses:
  535. break;
  536. case FLEXObjectExplorerSectionReferencingInstances:
  537. break;
  538. }
  539. return subtitle;
  540. }
  541. - (BOOL)canDrillInToRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  542. {
  543. BOOL canDrillIn = NO;
  544. switch (section) {
  545. case FLEXObjectExplorerSectionDescription:
  546. break;
  547. case FLEXObjectExplorerSectionCustom:
  548. canDrillIn = [self customSectionCanDrillIntoRowWithCookie:[self customSectionRowCookieForVisibleRow:row]];
  549. break;
  550. case FLEXObjectExplorerSectionProperties: {
  551. if ([self canHaveInstanceState]) {
  552. FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:row];
  553. objc_property_t property = propertyBox.property;
  554. id currentValue = [self valueForPropertyAtIndex:row];
  555. BOOL canEdit = [FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue];
  556. BOOL canExplore = currentValue != nil;
  557. canDrillIn = canEdit || canExplore;
  558. }
  559. } break;
  560. case FLEXObjectExplorerSectionIvars: {
  561. if ([self canHaveInstanceState]) {
  562. FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:row];
  563. Ivar ivar = ivarBox.ivar;
  564. id currentValue = [self valueForIvarAtIndex:row];
  565. BOOL canEdit = [FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue];
  566. BOOL canExplore = currentValue != nil;
  567. canDrillIn = canEdit || canExplore;
  568. }
  569. } break;
  570. case FLEXObjectExplorerSectionMethods:
  571. canDrillIn = [self canCallInstanceMethods];
  572. break;
  573. case FLEXObjectExplorerSectionClassMethods:
  574. canDrillIn = YES;
  575. break;
  576. case FLEXObjectExplorerSectionSuperclasses:
  577. canDrillIn = YES;
  578. break;
  579. case FLEXObjectExplorerSectionReferencingInstances:
  580. canDrillIn = YES;
  581. break;
  582. }
  583. return canDrillIn;
  584. }
  585. - (NSString *)titleForExplorerSection:(FLEXObjectExplorerSection)section
  586. {
  587. NSString *title = nil;
  588. switch (section) {
  589. case FLEXObjectExplorerSectionDescription: {
  590. title = @"Description";
  591. } break;
  592. case FLEXObjectExplorerSectionCustom: {
  593. title = [self customSectionTitle];
  594. } break;
  595. case FLEXObjectExplorerSectionProperties: {
  596. NSUInteger totalCount = [self.properties count];
  597. if (self.includeInheritance) {
  598. totalCount += [self.inheritedProperties count];
  599. }
  600. title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
  601. } break;
  602. case FLEXObjectExplorerSectionIvars: {
  603. NSUInteger totalCount = [self.ivars count];
  604. if (self.includeInheritance) {
  605. totalCount += [self.inheritedIvars count];
  606. }
  607. title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
  608. } break;
  609. case FLEXObjectExplorerSectionMethods: {
  610. NSUInteger totalCount = [self.methods count];
  611. if (self.includeInheritance) {
  612. totalCount += [self.inheritedMethods count];
  613. }
  614. title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
  615. } break;
  616. case FLEXObjectExplorerSectionClassMethods: {
  617. NSUInteger totalCount = [self.classMethods count];
  618. if (self.includeInheritance) {
  619. totalCount += [self.inheritedClassMethods count];
  620. }
  621. title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
  622. } break;
  623. case FLEXObjectExplorerSectionSuperclasses: {
  624. title = [self sectionTitleWithBaseName:@"Superclasses" totalCount:[self.superclasses count] filteredCount:[self.filteredSuperclasses count]];
  625. } break;
  626. case FLEXObjectExplorerSectionReferencingInstances: {
  627. title = @"Object Graph";
  628. } break;
  629. }
  630. return title;
  631. }
  632. - (UIViewController *)drillInViewControllerForRow:(NSUInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  633. {
  634. UIViewController *viewController = nil;
  635. switch (section) {
  636. case FLEXObjectExplorerSectionDescription:
  637. break;
  638. case FLEXObjectExplorerSectionCustom:
  639. viewController = [self customSectionDrillInViewControllerForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  640. break;
  641. case FLEXObjectExplorerSectionProperties: {
  642. FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:row];
  643. objc_property_t property = propertyBox.property;
  644. id currentValue = [self valueForPropertyAtIndex:row];
  645. if ([FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue]) {
  646. viewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.object property:property];
  647. } else if (currentValue) {
  648. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  649. }
  650. } break;
  651. case FLEXObjectExplorerSectionIvars: {
  652. FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:row];
  653. Ivar ivar = ivarBox.ivar;
  654. id currentValue = [self valueForIvarAtIndex:row];
  655. if ([FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue]) {
  656. viewController = [[FLEXIvarEditorViewController alloc] initWithTarget:self.object ivar:ivar];
  657. } else if (currentValue) {
  658. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  659. }
  660. } break;
  661. case FLEXObjectExplorerSectionMethods: {
  662. FLEXMethodBox *methodBox = [self.filteredMethods objectAtIndex:row];
  663. Method method = methodBox.method;
  664. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:self.object method:method];
  665. } break;
  666. case FLEXObjectExplorerSectionClassMethods: {
  667. FLEXMethodBox *methodBox = [self.filteredClassMethods objectAtIndex:row];
  668. Method method = methodBox.method;
  669. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:[self.object class] method:method];
  670. } break;
  671. case FLEXObjectExplorerSectionSuperclasses: {
  672. Class superclass = [self.filteredSuperclasses objectAtIndex:row];
  673. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:superclass];
  674. } break;
  675. case FLEXObjectExplorerSectionReferencingInstances: {
  676. viewController = [FLEXInstancesTableViewController instancesTableViewControllerForInstancesReferencingObject:self.object];
  677. } break;
  678. }
  679. return viewController;
  680. }
  681. #pragma mark - Table View Data Source
  682. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  683. {
  684. return [[self visibleExplorerSections] count];
  685. }
  686. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  687. {
  688. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  689. return [self numberOfRowsForExplorerSection:explorerSection];
  690. }
  691. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  692. {
  693. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  694. return [self titleForExplorerSection:explorerSection];
  695. }
  696. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  697. {
  698. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  699. BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
  700. NSString *cellIdentifier = useDescriptionCell ? @"descriptionCell" : @"cell";
  701. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  702. if (!cell) {
  703. if (useDescriptionCell) {
  704. cell = [[FLEXDescriptionTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
  705. } else {
  706. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
  707. UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
  708. cell.textLabel.font = cellFont;
  709. cell.detailTextLabel.font = cellFont;
  710. cell.detailTextLabel.textColor = [UIColor grayColor];
  711. }
  712. }
  713. cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  714. cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  715. cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
  716. return cell;
  717. }
  718. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  719. {
  720. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  721. CGFloat height = self.tableView.rowHeight;
  722. if (explorerSection == FLEXObjectExplorerSectionDescription) {
  723. NSString *text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  724. CGFloat preferredHeight = [FLEXDescriptionTableViewCell preferredHeightWithText:text inTableViewWidth:self.tableView.frame.size.width];
  725. height = MAX(height, preferredHeight);
  726. }
  727. return height;
  728. }
  729. #pragma mark - Table View Delegate
  730. - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
  731. {
  732. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  733. return [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection];
  734. }
  735. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  736. {
  737. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  738. UIViewController *detailViewController = [self drillInViewControllerForRow:indexPath.row inExplorerSection:explorerSection];
  739. if (detailViewController) {
  740. [self.navigationController pushViewController:detailViewController animated:YES];
  741. } else {
  742. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  743. }
  744. }
  745. #pragma mark - Custom Section
  746. - (void)updateCustomData
  747. {
  748. self.cachedCustomSectionRowCookies = [self customSectionRowCookies];
  749. }
  750. - (void)updateFilteredCustomData
  751. {
  752. NSIndexSet *filteredIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self.cachedCustomSectionRowCookies count])];
  753. if ([self.filterText length] > 0) {
  754. filteredIndexSet = [filteredIndexSet indexesPassingTest:^BOOL(NSUInteger index, BOOL *stop) {
  755. BOOL matches = NO;
  756. NSString *rowTitle = [self customSectionTitleForRowCookie:[self.cachedCustomSectionRowCookies objectAtIndex:index]];
  757. if ([rowTitle rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  758. matches = YES;
  759. }
  760. return matches;
  761. }];
  762. }
  763. self.customSectionVisibleIndexes = filteredIndexSet;
  764. }
  765. - (id)customSectionRowCookieForVisibleRow:(NSUInteger)row
  766. {
  767. return [[self.cachedCustomSectionRowCookies objectsAtIndexes:self.customSectionVisibleIndexes] objectAtIndex:row];
  768. }
  769. #pragma mark - Subclasses Can Override
  770. - (NSString *)customSectionTitle
  771. {
  772. return nil;
  773. }
  774. - (NSArray *)customSectionRowCookies
  775. {
  776. return nil;
  777. }
  778. - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
  779. {
  780. return nil;
  781. }
  782. - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
  783. {
  784. return nil;
  785. }
  786. - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
  787. {
  788. return NO;
  789. }
  790. - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
  791. {
  792. return nil;
  793. }
  794. - (BOOL)canHaveInstanceState
  795. {
  796. return YES;
  797. }
  798. - (BOOL)canCallInstanceMethods
  799. {
  800. return YES;
  801. }
  802. @end