FLEXObjectExplorerViewController.m 34 KB

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