FLEXObjectExplorerViewController.m 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  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. NSString *typeString = [FLEXRuntimeUtility typeEncodingForProperty:propertyBox.property];
  313. const FLEXTypeEncoding *encoding = [typeString cStringUsingEncoding:NSUTF8StringEncoding];
  314. value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
  315. value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
  316. }
  317. return value;
  318. }
  319. #pragma mark - Ivars
  320. - (void)updateIvars
  321. {
  322. Class class = [self.object class];
  323. self.ivars = [[self class] ivarsForClass:class];
  324. self.ivarsWithParent = [self.ivars arrayByAddingObjectsFromArray:[[self class] ivarsForClass:[class superclass]]];
  325. self.inheritedIvars = [self.ivars arrayByAddingObjectsFromArray:[[self class] inheritedIvarsForClass:class]];
  326. self.NSObjectIvars = [[self class] ivarsForClass:[NSObject class]];
  327. }
  328. + (NSArray<FLEXIvarBox *> *)ivarsForClass:(Class)class
  329. {
  330. if (!class) {
  331. return @[];
  332. }
  333. NSMutableArray<FLEXIvarBox *> *boxedIvars = [NSMutableArray array];
  334. unsigned int ivarCount = 0;
  335. Ivar *ivarList = class_copyIvarList(class, &ivarCount);
  336. if (ivarList) {
  337. for (unsigned int i = 0; i < ivarCount; i++) {
  338. FLEXIvarBox *ivarBox = [[FLEXIvarBox alloc] init];
  339. ivarBox.ivar = ivarList[i];
  340. [boxedIvars addObject:ivarBox];
  341. }
  342. free(ivarList);
  343. }
  344. return boxedIvars;
  345. }
  346. /// Skips NSObject
  347. + (NSArray<FLEXIvarBox *> *)inheritedIvarsForClass:(Class)class
  348. {
  349. NSMutableArray<FLEXIvarBox *> *inheritedIvars = [NSMutableArray array];
  350. while ((class = [class superclass]) && class != [NSObject class]) {
  351. [inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
  352. }
  353. return inheritedIvars;
  354. }
  355. - (void)updateFilteredIvars
  356. {
  357. NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forScope:self.scope];
  358. NSArray<FLEXIvarBox *> *unsortedFilteredIvars = nil;
  359. if ([self.filterText length] > 0) {
  360. NSMutableArray<FLEXIvarBox *> *mutableUnsortedFilteredIvars = [NSMutableArray array];
  361. for (FLEXIvarBox *ivarBox in candidateIvars) {
  362. NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  363. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  364. [mutableUnsortedFilteredIvars addObject:ivarBox];
  365. }
  366. }
  367. unsortedFilteredIvars = mutableUnsortedFilteredIvars;
  368. } else {
  369. unsortedFilteredIvars = candidateIvars;
  370. }
  371. self.filteredIvars = [unsortedFilteredIvars sortedArrayUsingComparator:^NSComparisonResult(FLEXIvarBox *ivarBox1, FLEXIvarBox *ivarBox2) {
  372. NSString *name1 = [NSString stringWithUTF8String:ivar_getName(ivarBox1.ivar)];
  373. NSString *name2 = [NSString stringWithUTF8String:ivar_getName(ivarBox2.ivar)];
  374. return [name1 caseInsensitiveCompare:name2];
  375. }];
  376. }
  377. - (NSString *)titleForIvarAtIndex:(NSInteger)index
  378. {
  379. FLEXIvarBox *ivarBox = self.filteredIvars[index];
  380. return [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  381. }
  382. - (id)valueForIvarAtIndex:(NSInteger)index
  383. {
  384. id value = nil;
  385. if ([self canHaveInstanceState]) {
  386. FLEXIvarBox *ivarBox = self.filteredIvars[index];
  387. const FLEXTypeEncoding *encoding = ivar_getTypeEncoding(ivarBox.ivar);
  388. value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
  389. value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
  390. }
  391. return value;
  392. }
  393. #pragma mark - Methods
  394. - (void)updateMethods
  395. {
  396. Class class = [self.object class];
  397. self.methods = [[self class] methodsForClass:class];
  398. self.methodsWithParent = [self.methods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[class superclass]]];
  399. self.inheritedMethods = [self.methods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:class]];
  400. self.NSObjectMethods = [[self class] methodsForClass:[NSObject class]];
  401. }
  402. - (void)updateFilteredMethods
  403. {
  404. NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forScope:self.scope];
  405. self.filteredMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:NO];
  406. }
  407. - (void)updateClassMethods
  408. {
  409. const char *className = [NSStringFromClass([self.object class]) UTF8String];
  410. Class metaClass = objc_getMetaClass(className);
  411. self.classMethods = [[self class] methodsForClass:metaClass];
  412. self.classMethodsWithParent = [self.classMethods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[metaClass superclass]]];
  413. self.inheritedClassMethods = [self.classMethods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:metaClass]];
  414. self.NSObjectClassMethods = [[self class] methodsForClass:[NSObject class]];
  415. }
  416. - (void)updateFilteredClassMethods
  417. {
  418. NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forScope:self.scope];
  419. self.filteredClassMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:YES];
  420. }
  421. + (NSArray<FLEXMethodBox *> *)methodsForClass:(Class)class
  422. {
  423. if (!class) {
  424. return @[];
  425. }
  426. NSMutableArray<FLEXMethodBox *> *boxedMethods = [NSMutableArray array];
  427. unsigned int methodCount = 0;
  428. Method *methodList = class_copyMethodList(class, &methodCount);
  429. if (methodList) {
  430. for (unsigned int i = 0; i < methodCount; i++) {
  431. FLEXMethodBox *methodBox = [[FLEXMethodBox alloc] init];
  432. methodBox.method = methodList[i];
  433. [boxedMethods addObject:methodBox];
  434. }
  435. free(methodList);
  436. }
  437. return boxedMethods;
  438. }
  439. /// Skips NSObject
  440. + (NSArray<FLEXMethodBox *> *)inheritedMethodsForClass:(Class)class
  441. {
  442. NSMutableArray<FLEXMethodBox *> *inheritedMethods = [NSMutableArray array];
  443. while ((class = [class superclass]) && class != [NSObject class]) {
  444. [inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
  445. }
  446. return inheritedMethods;
  447. }
  448. - (NSArray<FLEXMethodBox *> *)filteredMethodsFromMethods:(NSArray<FLEXMethodBox *> *)methods areClassMethods:(BOOL)areClassMethods
  449. {
  450. NSArray<FLEXMethodBox *> *candidateMethods = methods;
  451. NSArray<FLEXMethodBox *> *unsortedFilteredMethods = nil;
  452. if ([self.filterText length] > 0) {
  453. NSMutableArray<FLEXMethodBox *> *mutableUnsortedFilteredMethods = [NSMutableArray array];
  454. for (FLEXMethodBox *methodBox in candidateMethods) {
  455. NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
  456. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  457. [mutableUnsortedFilteredMethods addObject:methodBox];
  458. }
  459. }
  460. unsortedFilteredMethods = mutableUnsortedFilteredMethods;
  461. } else {
  462. unsortedFilteredMethods = candidateMethods;
  463. }
  464. NSArray<FLEXMethodBox *> *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
  465. NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
  466. NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
  467. return [name1 caseInsensitiveCompare:name2];
  468. }];
  469. return sortedFilteredMethods;
  470. }
  471. - (NSString *)titleForMethodAtIndex:(NSInteger)index
  472. {
  473. FLEXMethodBox *methodBox = self.filteredMethods[index];
  474. return [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:NO];
  475. }
  476. - (NSString *)titleForClassMethodAtIndex:(NSInteger)index
  477. {
  478. FLEXMethodBox *classMethodBox = self.filteredClassMethods[index];
  479. return [FLEXRuntimeUtility prettyNameForMethod:classMethodBox.method isClassMethod:YES];
  480. }
  481. #pragma mark - Superclasses
  482. + (NSArray<Class> *)superclassesForClass:(Class)class
  483. {
  484. NSMutableArray<Class> *superClasses = [NSMutableArray array];
  485. while ((class = [class superclass])) {
  486. [superClasses addObject:class];
  487. }
  488. return superClasses;
  489. }
  490. - (void)updateSuperclasses
  491. {
  492. self.superclasses = [[self class] superclassesForClass:[self.object class]];
  493. }
  494. - (void)updateFilteredSuperclasses
  495. {
  496. if ([self.filterText length] > 0) {
  497. NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
  498. for (Class superclass in self.superclasses) {
  499. if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
  500. [filteredSuperclasses addObject:superclass];
  501. }
  502. }
  503. self.filteredSuperclasses = filteredSuperclasses;
  504. } else {
  505. self.filteredSuperclasses = self.superclasses;
  506. }
  507. }
  508. #pragma mark - Table View Data Helpers
  509. - (NSArray<NSNumber *> *)possibleExplorerSections
  510. {
  511. static NSArray<NSNumber *> *possibleSections = nil;
  512. static dispatch_once_t onceToken;
  513. dispatch_once(&onceToken, ^{
  514. possibleSections = @[@(FLEXObjectExplorerSectionDescription),
  515. @(FLEXObjectExplorerSectionCustom),
  516. @(FLEXObjectExplorerSectionProperties),
  517. @(FLEXObjectExplorerSectionIvars),
  518. @(FLEXObjectExplorerSectionMethods),
  519. @(FLEXObjectExplorerSectionClassMethods),
  520. @(FLEXObjectExplorerSectionSuperclasses),
  521. @(FLEXObjectExplorerSectionReferencingInstances)];
  522. });
  523. return possibleSections;
  524. }
  525. - (NSArray<NSNumber *> *)visibleExplorerSections
  526. {
  527. NSMutableArray<NSNumber *> *visibleSections = [NSMutableArray array];
  528. for (NSNumber *possibleSection in [self possibleExplorerSections]) {
  529. FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
  530. if ([self numberOfRowsForExplorerSection:explorerSection] > 0) {
  531. [visibleSections addObject:possibleSection];
  532. }
  533. }
  534. return visibleSections;
  535. }
  536. - (NSString *)sectionTitleWithBaseName:(NSString *)baseName totalCount:(NSUInteger)totalCount filteredCount:(NSUInteger)filteredCount
  537. {
  538. NSString *sectionTitle = nil;
  539. if (totalCount == filteredCount) {
  540. sectionTitle = [baseName stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
  541. } else {
  542. sectionTitle = [baseName stringByAppendingFormat:@" (%lu of %lu)", (unsigned long)filteredCount, (unsigned long)totalCount];
  543. }
  544. return sectionTitle;
  545. }
  546. - (FLEXObjectExplorerSection)explorerSectionAtIndex:(NSInteger)sectionIndex
  547. {
  548. return [[[self visibleExplorerSections] objectAtIndex:sectionIndex] unsignedIntegerValue];
  549. }
  550. - (NSInteger)numberOfRowsForExplorerSection:(FLEXObjectExplorerSection)section
  551. {
  552. NSInteger numberOfRows = 0;
  553. switch (section) {
  554. case FLEXObjectExplorerSectionDescription:
  555. numberOfRows = [self shouldShowDescription] ? 1 : 0;
  556. break;
  557. case FLEXObjectExplorerSectionCustom:
  558. numberOfRows = [self.customSectionVisibleIndexes count];
  559. break;
  560. case FLEXObjectExplorerSectionProperties:
  561. numberOfRows = [self.filteredProperties count];
  562. break;
  563. case FLEXObjectExplorerSectionIvars:
  564. numberOfRows = [self.filteredIvars count];
  565. break;
  566. case FLEXObjectExplorerSectionMethods:
  567. numberOfRows = [self.filteredMethods count];
  568. break;
  569. case FLEXObjectExplorerSectionClassMethods:
  570. numberOfRows = [self.filteredClassMethods count];
  571. break;
  572. case FLEXObjectExplorerSectionSuperclasses:
  573. numberOfRows = [self.filteredSuperclasses count];
  574. break;
  575. case FLEXObjectExplorerSectionReferencingInstances:
  576. // Hide this section if there is fliter text since there's nothing searchable (only 1 row, always the same).
  577. numberOfRows = [self.filterText length] == 0 ? 1 : 0;
  578. break;
  579. }
  580. return numberOfRows;
  581. }
  582. - (NSString *)titleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  583. {
  584. NSString *title = nil;
  585. switch (section) {
  586. case FLEXObjectExplorerSectionDescription:
  587. title = [FLEXUtility safeDescriptionForObject:self.object];
  588. break;
  589. case FLEXObjectExplorerSectionCustom:
  590. title = [self customSectionTitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  591. break;
  592. case FLEXObjectExplorerSectionProperties:
  593. title = [self titleForPropertyAtIndex:row];
  594. break;
  595. case FLEXObjectExplorerSectionIvars:
  596. title = [self titleForIvarAtIndex:row];
  597. break;
  598. case FLEXObjectExplorerSectionMethods:
  599. title = [self titleForMethodAtIndex:row];
  600. break;
  601. case FLEXObjectExplorerSectionClassMethods:
  602. title = [self titleForClassMethodAtIndex:row];
  603. break;
  604. case FLEXObjectExplorerSectionSuperclasses:
  605. title = NSStringFromClass(self.filteredSuperclasses[row]);
  606. break;
  607. case FLEXObjectExplorerSectionReferencingInstances:
  608. title = @"Other objects with ivars referencing this object";
  609. break;
  610. }
  611. return title;
  612. }
  613. - (NSString *)subtitleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  614. {
  615. NSString *subtitle = nil;
  616. switch (section) {
  617. case FLEXObjectExplorerSectionDescription:
  618. break;
  619. case FLEXObjectExplorerSectionCustom:
  620. subtitle = [self customSectionSubtitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  621. break;
  622. case FLEXObjectExplorerSectionProperties:
  623. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForPropertyAtIndex:row]] : nil;
  624. break;
  625. case FLEXObjectExplorerSectionIvars:
  626. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForIvarAtIndex:row]] : nil;
  627. break;
  628. case FLEXObjectExplorerSectionMethods:
  629. break;
  630. case FLEXObjectExplorerSectionClassMethods:
  631. break;
  632. case FLEXObjectExplorerSectionSuperclasses:
  633. break;
  634. case FLEXObjectExplorerSectionReferencingInstances:
  635. break;
  636. }
  637. return subtitle;
  638. }
  639. - (BOOL)canDrillInToRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  640. {
  641. BOOL canDrillIn = NO;
  642. switch (section) {
  643. case FLEXObjectExplorerSectionDescription:
  644. break;
  645. case FLEXObjectExplorerSectionCustom:
  646. canDrillIn = [self customSectionCanDrillIntoRowWithCookie:[self customSectionRowCookieForVisibleRow:row]];
  647. break;
  648. case FLEXObjectExplorerSectionProperties: {
  649. if ([self canHaveInstanceState]) {
  650. FLEXPropertyBox *propertyBox = self.filteredProperties[row];
  651. objc_property_t property = propertyBox.property;
  652. id currentValue = [self valueForPropertyAtIndex:row];
  653. BOOL canEdit = [FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue];
  654. BOOL canExplore = currentValue != nil;
  655. canDrillIn = canEdit || canExplore;
  656. }
  657. } break;
  658. case FLEXObjectExplorerSectionIvars: {
  659. if ([self canHaveInstanceState]) {
  660. FLEXIvarBox *ivarBox = self.filteredIvars[row];
  661. Ivar ivar = ivarBox.ivar;
  662. id currentValue = [self valueForIvarAtIndex:row];
  663. BOOL canEdit = [FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue];
  664. BOOL canExplore = currentValue != nil;
  665. canDrillIn = canEdit || canExplore;
  666. }
  667. } break;
  668. case FLEXObjectExplorerSectionMethods:
  669. canDrillIn = [self canCallInstanceMethods];
  670. break;
  671. case FLEXObjectExplorerSectionClassMethods:
  672. canDrillIn = YES;
  673. break;
  674. case FLEXObjectExplorerSectionSuperclasses:
  675. canDrillIn = YES;
  676. break;
  677. case FLEXObjectExplorerSectionReferencingInstances:
  678. canDrillIn = YES;
  679. break;
  680. }
  681. return canDrillIn;
  682. }
  683. - (BOOL)canCopyRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  684. {
  685. BOOL canCopy = NO;
  686. switch (section) {
  687. case FLEXObjectExplorerSectionDescription:
  688. canCopy = YES;
  689. break;
  690. default:
  691. break;
  692. }
  693. return canCopy;
  694. }
  695. - (NSString *)titleForExplorerSection:(FLEXObjectExplorerSection)section
  696. {
  697. NSString *title = nil;
  698. switch (section) {
  699. case FLEXObjectExplorerSectionDescription: {
  700. title = @"Description";
  701. } break;
  702. case FLEXObjectExplorerSectionCustom: {
  703. title = [self customSectionTitle];
  704. } break;
  705. case FLEXObjectExplorerSectionProperties: {
  706. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forScope:self.scope];
  707. title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
  708. } break;
  709. case FLEXObjectExplorerSectionIvars: {
  710. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forScope:self.scope];
  711. title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
  712. } break;
  713. case FLEXObjectExplorerSectionMethods: {
  714. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forScope:self.scope];
  715. title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
  716. } break;
  717. case FLEXObjectExplorerSectionClassMethods: {
  718. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forScope:self.scope];
  719. title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
  720. } break;
  721. case FLEXObjectExplorerSectionSuperclasses: {
  722. title = [self sectionTitleWithBaseName:@"Superclasses" totalCount:[self.superclasses count] filteredCount:[self.filteredSuperclasses count]];
  723. } break;
  724. case FLEXObjectExplorerSectionReferencingInstances: {
  725. title = @"Object Graph";
  726. } break;
  727. }
  728. return title;
  729. }
  730. - (UIViewController *)drillInViewControllerForRow:(NSUInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  731. {
  732. UIViewController *viewController = nil;
  733. switch (section) {
  734. case FLEXObjectExplorerSectionDescription:
  735. break;
  736. case FLEXObjectExplorerSectionCustom:
  737. viewController = [self customSectionDrillInViewControllerForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  738. break;
  739. case FLEXObjectExplorerSectionProperties: {
  740. FLEXPropertyBox *propertyBox = self.filteredProperties[row];
  741. objc_property_t property = propertyBox.property;
  742. id currentValue = [self valueForPropertyAtIndex:row];
  743. if ([FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue]) {
  744. viewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.object property:property];
  745. } else if (currentValue) {
  746. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  747. }
  748. } break;
  749. case FLEXObjectExplorerSectionIvars: {
  750. FLEXIvarBox *ivarBox = self.filteredIvars[row];
  751. Ivar ivar = ivarBox.ivar;
  752. id currentValue = [self valueForIvarAtIndex:row];
  753. if ([FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue]) {
  754. viewController = [[FLEXIvarEditorViewController alloc] initWithTarget:self.object ivar:ivar];
  755. } else if (currentValue) {
  756. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  757. }
  758. } break;
  759. case FLEXObjectExplorerSectionMethods: {
  760. FLEXMethodBox *methodBox = self.filteredMethods[row];
  761. Method method = methodBox.method;
  762. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:self.object method:method];
  763. } break;
  764. case FLEXObjectExplorerSectionClassMethods: {
  765. FLEXMethodBox *methodBox = self.filteredClassMethods[row];
  766. Method method = methodBox.method;
  767. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:[self.object class] method:method];
  768. } break;
  769. case FLEXObjectExplorerSectionSuperclasses: {
  770. Class superclass = self.filteredSuperclasses[row];
  771. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:superclass];
  772. } break;
  773. case FLEXObjectExplorerSectionReferencingInstances: {
  774. viewController = [FLEXInstancesTableViewController instancesTableViewControllerForInstancesReferencingObject:self.object];
  775. } break;
  776. }
  777. return viewController;
  778. }
  779. #pragma mark - Table View Data Source
  780. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  781. {
  782. return [[self visibleExplorerSections] count];
  783. }
  784. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  785. {
  786. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  787. return [self numberOfRowsForExplorerSection:explorerSection];
  788. }
  789. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  790. {
  791. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  792. return [self titleForExplorerSection:explorerSection];
  793. }
  794. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  795. {
  796. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  797. BOOL isCustomSection = explorerSection == FLEXObjectExplorerSectionCustom;
  798. BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
  799. NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineTableViewCellIdentifier : @"cell";
  800. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  801. if (!cell) {
  802. if (useDescriptionCell) {
  803. cell = [[FLEXMultilineTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
  804. cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
  805. } else {
  806. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
  807. UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
  808. cell.textLabel.font = cellFont;
  809. cell.detailTextLabel.font = cellFont;
  810. cell.detailTextLabel.textColor = [UIColor grayColor];
  811. }
  812. }
  813. UIView *customView;
  814. if (isCustomSection) {
  815. customView = [self customViewForRowCookie:[self customSectionRowCookieForVisibleRow:indexPath.row]];
  816. if (customView) {
  817. [cell.contentView addSubview:customView];
  818. }
  819. }
  820. cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  821. cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  822. cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
  823. return cell;
  824. }
  825. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  826. {
  827. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  828. CGFloat height = self.tableView.rowHeight;
  829. if (explorerSection == FLEXObjectExplorerSectionDescription) {
  830. NSString *text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  831. NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont] }];
  832. CGFloat preferredHeight = [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.frame.size.width style:tableView.style showsAccessory:NO];
  833. height = MAX(height, preferredHeight);
  834. } else if (explorerSection == FLEXObjectExplorerSectionCustom) {
  835. id cookie = [self customSectionRowCookieForVisibleRow:indexPath.row];
  836. height = [self heightForCustomViewRowForRowCookie:cookie];
  837. }
  838. return height;
  839. }
  840. #pragma mark - Table View Delegate
  841. - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
  842. {
  843. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  844. return [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection];
  845. }
  846. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  847. {
  848. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  849. UIViewController *detailViewController = [self drillInViewControllerForRow:indexPath.row inExplorerSection:explorerSection];
  850. if (detailViewController) {
  851. [self.navigationController pushViewController:detailViewController animated:YES];
  852. } else {
  853. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  854. }
  855. }
  856. - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
  857. {
  858. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  859. BOOL canCopy = [self canCopyRow:indexPath.row inExplorerSection:explorerSection];
  860. return canCopy;
  861. }
  862. - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  863. {
  864. BOOL canPerformAction = NO;
  865. if (action == @selector(copy:)) {
  866. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  867. BOOL canCopy = [self canCopyRow:indexPath.row inExplorerSection:explorerSection];
  868. canPerformAction = canCopy;
  869. }
  870. return canPerformAction;
  871. }
  872. - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  873. {
  874. if (action == @selector(copy:)) {
  875. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  876. NSString *stringToCopy = @"";
  877. NSString *title = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  878. if ([title length] > 0) {
  879. stringToCopy = [stringToCopy stringByAppendingString:title];
  880. }
  881. NSString *subtitle = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  882. if ([subtitle length] > 0) {
  883. if ([stringToCopy length] > 0) {
  884. stringToCopy = [stringToCopy stringByAppendingString:@"\n\n"];
  885. }
  886. stringToCopy = [stringToCopy stringByAppendingString:subtitle];
  887. }
  888. [[UIPasteboard generalPasteboard] setString:stringToCopy];
  889. }
  890. }
  891. #pragma mark - Custom Section
  892. - (void)updateCustomData
  893. {
  894. self.cachedCustomSectionRowCookies = [self customSectionRowCookies];
  895. }
  896. - (void)updateFilteredCustomData
  897. {
  898. NSIndexSet *filteredIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self.cachedCustomSectionRowCookies count])];
  899. if ([self.filterText length] > 0) {
  900. filteredIndexSet = [filteredIndexSet indexesPassingTest:^BOOL(NSUInteger index, BOOL *stop) {
  901. BOOL matches = NO;
  902. NSString *rowTitle = [self customSectionTitleForRowCookie:self.cachedCustomSectionRowCookies[index]];
  903. if ([rowTitle rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  904. matches = YES;
  905. }
  906. return matches;
  907. }];
  908. }
  909. self.customSectionVisibleIndexes = filteredIndexSet;
  910. }
  911. - (id)customSectionRowCookieForVisibleRow:(NSUInteger)row
  912. {
  913. return [[self.cachedCustomSectionRowCookies objectsAtIndexes:self.customSectionVisibleIndexes] objectAtIndex:row];
  914. }
  915. #pragma mark - Subclasses Can Override
  916. - (NSString *)customSectionTitle
  917. {
  918. return nil;
  919. }
  920. - (NSArray *)customSectionRowCookies
  921. {
  922. return nil;
  923. }
  924. - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
  925. {
  926. return nil;
  927. }
  928. - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
  929. {
  930. return nil;
  931. }
  932. - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
  933. {
  934. return NO;
  935. }
  936. - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
  937. {
  938. return nil;
  939. }
  940. - (UIView *)customViewForRowCookie:(id)rowCookie
  941. {
  942. return nil;
  943. }
  944. - (CGFloat)heightForCustomViewRowForRowCookie:(id)rowCookie
  945. {
  946. return self.tableView.rowHeight;
  947. }
  948. - (BOOL)canHaveInstanceState
  949. {
  950. return YES;
  951. }
  952. - (BOOL)canCallInstanceMethods
  953. {
  954. return YES;
  955. }
  956. @end