FLEXObjectExplorerViewController.m 32 KB

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