FLEXObjectExplorerViewController.m 42 KB

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