FLEXObjectExplorerViewController.m 43 KB

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