FLEXObjectExplorerViewController.m 30 KB

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