FLEXObjectExplorerViewController.m 31 KB

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