FLEXObjectExplorerViewController.m 36 KB

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